Last time, we noted that the std::rotate function requires only forward iterators. The algorithm we explored last time involved a clever sequence of reversals, but reversals require bidirectional iterators (one to iterator forward from the beginning and another to iterate backward from the end). How do you do it when you can only iterate forward?
Let’s set up the problem again. Suppose we want to swap these two blocks A and B.
| A | B | |
| ↑ | ↑ | ↑ |
| first | mid | last |
We can early-out in the vacuous cases where either the A or B block is empty. For those cases, we can return without doing anything.
We start by swapping the elements at first and mid, then incrementing both pointers and repeating. Here’s what it looks after swapping a handful of items:
| B1 | A2 | A1 | B2 | |
| ↑ | ↑ | ↑ | ||
| first | mid | last |
Let’s first take the case where the A block is bigger than the B block. In that case, the entire B block gets swapped to the start of the buffer, and a B-sized chunk of the first part of the A block (let’s call it A1) got swapped to the end of the buffer. There’s still a leftover chunk of the A block, call it A2, that hasn’t moved yet.
| B | A2 | A1 | |
| ↑ | ↑ | ||
| first | mid last |
Okay, so the B block is in its final position, but the A1 and A2 blocks need to be swapped. Hey, I know how to swap two adjacent blocks: I call std::rotate on myself recursively! Since this is a tail recursive call, I can just move mid back to its original position at the start of the function and restart the algorithm.
| B | A2 | A1 | ||
| ↑ | ↑ | ↑ | ||
| first | mid | last | ||
| ⤩ | recursive rotate to swap A1 and A2 | |||
| B | A1 | A2 | ||
The other case is where the A block is smaller than the B block.
| A | B | |
| ↑ | ↑ | ↑ |
| first | mid | last |
In this case, we swap until we have used up all of the A elements. The entire A block gets swapped to the middle of the buffer, and an A-sized chunk of the start of the B block (call it B1) goes to the start of the buffer, with the leftover part of B (call it B2) stays at the end of the buffer.
| B1 | A | B2 | |
| ↑ | ↑ | ↑ | |
| first | mid | last |
Again, we can finish with a recursive rotate call, this time to swap A and B2.
| B1 | A | B2 | ||
| ⤩ | recursive rotate to swap A and B2 | |||
| B1 | B2 | A | ||
In both cases, the recursive call is strictly smaller than the original call because we know that both A and B are nonempty: If either was empty, we would have performed a nop early-exit. Therefore, at least one element will be swapped before we reach the recursive call, and we consequently know that the recursion will eventually terminate.
The final case is where the A and B blocks are the same size. In that case, we have swapped all the B elements to the first half and all the A elements to the second half.
| B | A | |
| ↑ | ↑ | |
| first | mid last |
If we are lucky to get here, then we are done!
Note that we don’t need a special case handler for the equal-sized blocks case. We can treat as either the first or second case and let the early-out in the recursive call realize that there is nothing to do.
The number of swaps is n. You can calculate this recursively, since we perform k swaps to move the smaller block, and the recursive call (by induction) performs n − k swaps, for a total of n. You can also calculate this directly by observing that at each step, one element gets swapped into its final position (namely, at *first, just before we increment it).
Even though this algorithm and the bidirectional-iterator version both perform n swaps, the bidirectional-iterator version has better locality of reference, so it is preferred if available.
Next time, we’ll apply this to our original problem.
Bonus chatter: You don’t need to do a preliminary std::distance to figure out whether block A or block B is smaller. You can just start swapping elements, and make a note of which happens first: Does first reach the original mid (which means that the A block is smaller) or does mid reach last (which means that the B block is smaller). This “figure it out as you go” doesn’t change the complexity, but it does lower the constant a little bit because you don’t have to iterate through all of the larger block just to realize that it’s took big.
The post How can you swap two adjacent blocks of memory using only forward iterators? appeared first on The Old New Thing.
TL;DR: Struggling with cluttered grids? Row and column spanning in Blazor DataGrid solves this by merging cells intelligently. Learn how to enable AutoSpan, handle merge/unmerge operations, and apply best practices for performance and usability.
Modern web apps demand clean, readable tables. When the same value repeats across rows or columns, grids can look cluttered and hard to scan. Syncfusion’s Blazor DataGrid solves this with row and column spanning, automatically merging duplicate values into a single cell. The result? A cleaner, more user-friendly table, without extra coding.
This feature has been introduced as part of the Syncfusion® 2025 Volume 4 release, which brings exciting updates across the suite.
In this guide, you’ll learn:
Don’t worry if you’re new to this; everything is explained in a simple, easy-to-understand way.
Row and Column spanning is enabled by using the AutoSpan property on your SfGrid tag. When adjacent cells share the same value, the grid merges them into a single cell.
The table below lists the available AutoSpanMode options:
| Settings | Description |
| AutoSpanMode.None | No merging(default). |
| AutoSpanMode.Row | Merges matching cells horizontally in the same row. |
| AutoSpanMode.Column | Merges matching cells vertically in the same column. |
| AutoSpanMode.HorizontalAndVertical | Combines both row and column spans (rows first, then columns). |
Here are some example use cases:
Row spanning merges identical values across multiple columns in the same row. For example, if Evening News appears twice in a row, the grid combines them into one wider cell.
Here’s how you can do it in code:
@using Syncfusion.Blazor.Grids
<SfGrid DataSource="@TeleCastDataList"
GridLines="GridLine.Both"
AutoSpan="AutoSpanMode.Row"
AllowSelection="false"
EnableHover="false">
<GridColumns>
<GridColumn Field=@nameof(TelecastData.Channel) HeaderText="Channel" Width="200" IsFrozen="true"></GridColumn>
<GridColumn Field=@nameof(TelecastData.Genre) HeaderText="Genre" Width="120" IsFrozen="true"></GridColumn>
<GridColumn Field=@nameof(TelecastData.Program12AM) HeaderText="12:00 AM" Width="150"></GridColumn>
<!-- ... other time slot columns ... -->
</GridColumns>
</SfGrid>
Note: Try it out with the row spanning demo and refer to the documentation for more insights.
Watch how the feature works in action:

Column spanning merges identical values vertically across consecutive rows. For example, lunch might cover multiple hourly slots for one employee.
Code snippet to achieve this:
@using Syncfusion.Blazor.Grids
<SfGrid DataSource="@EmployeeTimeSheet"
AutoSpan="AutoSpanMode.Column">
<GridColumns>
<!-- ... column definitions ... -->
</GridColumns>
</SfGrid>
Matching entries in a column are displayed in a taller cell, allowing you to see the pattern, as shown below.

Note: Try it out with the column spanning demo and refer to the documentation for more information.
Need spanning everywhere except one column? For example, if you don’t want prime-time TV slots to merge, set the AutoSpan property to AutoSpanMode.None in that column to override the grid settings.
<GridColumn Field=@nameof(TelecastData.Program8PM)
HeaderText="8:00 PM"
AutoSpan="AutoSpanMode.None">
</GridColumn>
Note: For more insights, refer to the documentation on disabling row and column spanning.
Auto spanning is great, but sometimes you need manual control. Syncfusion provides the following methods for custom merging:
Here’s a basic code example with buttons to merge and unmerge single or multiple cells:
@using Syncfusion.Blazor.Grids
<SfButton OnClick="MergeCellsAsync">Merge Cell</SfButton>
<SfButton OnClick="UnMergeCell">UnMerge Cell</SfButton>
<SfButton OnClick="MergeMultipleCellsAsync">Merge Multiple Cells</SfButton>
<SfButton OnClick="UnMergeCells">UnMerge Multiple Cells</SfButton>
<SfButton OnClick="UnMergeAllCells">UnMerge All Cells</SfButton>
<SfGrid @ref="Grid" DataSource="@EmployeeTimeSheet" GridLines="GridLine.Both" AllowSelection="false" EnableHover="false">
<GridColumns>
<GridColumn Field=@nameof(EmployeeDetails.EmployeeID) HeaderText="Employee ID" Width="150" TextAlign="TextAlign.Right" IsPrimaryKey="true" IsFrozen="true"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.EmployeeName) HeaderText="Employee Name" Width="180" IsFrozen="true"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_9_00) HeaderText="9:00 AM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_9_30) HeaderText="9:30 AM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_10_00) HeaderText="10:00 AM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_10_30) HeaderText="10:30 AM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_11_00) HeaderText="11:00 AM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_11_30) HeaderText="11:30 AM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_12_00) HeaderText="12:00 PM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_12_30) HeaderText="12:30 PM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_1_00) HeaderText="1:00 PM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_1_30) HeaderText="1:30 PM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_2_00) HeaderText="2:00 PM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_2_30) HeaderText="2:30 PM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_3_00) HeaderText="3:00 PM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_3_30) HeaderText="3:30 PM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_4_00) HeaderText="4:00 PM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_4_30) HeaderText="4:30 PM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
<GridColumn Field=@nameof(EmployeeDetails.Time_5_00) HeaderText="5:00 PM" Width="150" TextAlign="TextAlign.Center"></GridColumn>
</GridColumns>
</SfGrid>
@code
{
public List<EmployeeDetails>? EmployeeTimeSheet { get; set; }
public SfGrid<EmployeeDetails>? Grid;
protected override void OnInitialized()
{
EmployeeTimeSheet = EmployeeDetails.GetAllRecords();
}
public async Task MergeCellsAsync()
{
await Grid.MergeCellsAsync(new MergeCellInfo
{
RowIndex = 1,
ColumnIndex = 5,
ColumnSpan = 2,
});
}
public async Task UnMergeCell()
{
await Grid.UnmergeCellsAsync(new UnmergeCellInfo
{
RowIndex = 1,
ColumnIndex = 5,
});
}
public async Task MergeMultipleCellsAsync()
{
await Grid.MergeCellsAsync(new[]
{
new MergeCellInfo { RowIndex = 0, ColumnIndex = 2, ColumnSpan = 2 },
new MergeCellInfo { RowIndex = 5, ColumnIndex = 3, ColumnSpan = 3 },
new MergeCellInfo { RowIndex = 7, ColumnIndex = 4, ColumnSpan = 2 }
});
}
public async Task UnMergeCells()
{
await Grid.UnmergeCellsAsync(new[]
{
new UnmergeCellInfo { RowIndex = 0, ColumnIndex = 2 },
new UnmergeCellInfo { RowIndex = 5, ColumnIndex = 3 },
new UnmergeCellInfo { RowIndex = 7, ColumnIndex = 4 }
});
}
public async Task UnMergeAllCells()
{
await Grid.UnmergeAllAsync();
}
}
Before you implement row and column spanning, it’s important to understand a few constraints and best practices to ensure smooth functionality:
This feature has limitations with:
Here are some quick tips to optimize your grid:
Thank you for reading! Row and column spanning in Syncfusion Blazor DataGrid, introduced in the Essential Studio® 2025 Volume 4 release, helps you create cleaner, more readable tables with just a few lines of code. It’s perfect for schedules, lists, or any data with repeats. Try it out in the demos.
Check out our Release Notes and What’s New pages to see the other updates in this release, and leave your feedback in the comments section below. We would love to hear from you.
If you’re a Syncfusion user, you can download the setup from the license and downloads page. Otherwise, you can download a free 30-day trial.
You can also contact us through our support forum, support portal, or feedback portal for queries. We are always happy to assist you!
With Progress Agentic RAG, you can index appropriate data to provide an LLM more relevant, accurate information. And you can do this in a no-code interface. Let’s explore the dashboard together!
Large language models (LLMs) can only rely on the data they were trained on. Still, organizations often need AI assistants that can answer questions about specific or internal documentation that isn’t publicly available. Retrieval-augmented generation (RAG) addresses this by fetching relevant documents from a knowledge base and using them to enrich an LLM’s response.
For more information on RAG and how it works, refer to our previous articles on the topic: Understanding RAG (Retrieval-Augmented Generation) and What Are Embeddings?.
Let’s look at how to put this into practice with Progress Agentic RAG. While Agentic RAG is often used as an API-driven platform designed for developers, it also provides a powerful no-code interface. This allows users to create Knowledge Boxes, index data and deploy RAG-enabled search engines without writing a single line of code.
In this article, we’ll use Progress Agentic RAG to index a real document (the Progress Software Q3 2025 Earnings Report) and demonstrate how the platform retrieves accurate, source-cited answers to natural language questions.
When you first log into Progress Agentic RAG, you land on the Home dashboard for a Knowledge Box. A Knowledge Box is a searchable knowledge base, a container for all the documents and data we want to query.

The dashboard provides a quick overview of our Knowledge Box:
The left navigation bar gives us access to the core features: Upload data, Resources list, Synchronize, Search, Widgets and Advanced settings. For this walk-through, we’ll focus on uploading data and searching.
When we click on Upload data in the left navigation bar, we’ll see several options for adding content to our Knowledge Box. These include:

For this article, we’ll upload a single PDF: the Progress Software Q3 2025 Earnings Report.

We’re deliberately using a small, very recent source that LLMs are unlikely to have seen, making retrieval clearly observable.
We’ll click on the File upload option and upload our PDF. Once the PDF is uploaded, Progress Agentic RAG will begin the processing to extract the text, chunk it appropriately, generate embeddings, and index everything for semantic search. Once processed, the file will then appear in the Resources list:

With our data indexed, we can now query it. This is where the power of RAG becomes apparent. We aren’t just searching for keywords; we’re asking the AI to read, understand and synthesize answers based only on the document we provided.
Let’s look at three examples using the earnings report we just uploaded.
First, let’s ask for hard numbers. A standard keyword search might highlight where “revenue” appears, but RAG can construct a complete sentence answering the specific question.
Question: “What was Progress Software’s total revenue for the three months ended August 31, 2025, and what was the year-over-year growth rate?”
Answer: Progress Software’s total revenue for the three months ended August 31, 2025, was $249,795,000, and the year-over-year growth rate was 40%.

The answer includes numbered citations that link back to specific sections of the source document. Clicking through reveals exactly where in the PDF this information appears:

How cool is that! This traceability is critical for enterprise use cases that require verifying the accuracy of AI-generated responses.
Next, let’s ask a question that isn’t purely numerical and instead requires understanding the CEO’s quote and its intent.
Question: “According to CEO Yogesh Gupta, what product capabilities is Progress investing in and why?”
Answer: According to CEO Yogesh Gupta, Progress is investing in AI capabilities, including agentic RAG technology. This investment aims to empower customers to extract greater value from their data using GenAI.

This query demonstrates semantic understanding. The system locates the CEO’s quote within the document and extracts the relevant context about AI capabilities and agentic RAG technology, even though the question doesn’t use those exact terms.
Lastly, let’s examine how RAG handles forward-looking financial projections, which are often presented in separate guidance tables.
Question: “What is Progress Software’s updated fiscal year 2025 revenue guidance range?”
Answer: Progress Software’s updated fiscal year 2025 revenue guidance range is $975 million to $981 million.

Here, the system synthesizes information from the guidance tables in the report, demonstrating its ability to parse structured financial data and return precise figures.
In just a few minutes, we went from a raw PDF to a fully searchable, AI-powered knowledge base. Progress Agentic RAG handles the heavy lifting (document processing, chunking, embedding generation and retrieval) so we can focus on asking questions and getting answers.
The no-code interface makes it accessible to anyone, but Progress Agentic RAG also offers a comprehensive API for developers who want to integrate RAG capabilities into their own applications. We’ll explore the API-driven approach more in upcoming articles.
For more details, check out the following resources:
If this is the kind of SQL Server stuff you love learning about, you’ll love my training. I’m offering a 25% discount to my blog readers if you click from here. I’m also available for consulting if you just don’t have time for that, and need to solve database performance problems quickly. You can also get a quick, low cost health check with no phone time required.
The post Optimizer Rules I Wish SQL Server Had appeared first on Darling Data.