Identify any missing periods and combine the years into a single range

I am working on restructuring year ranges with gaps and consolidating them. For example, converting [{start: 2002, end: 2020}, {start: 2020, end: null}] to {start: 2002, end: null} or [{2002, 2004},{2006, 2008}, {2008, null}] to [{2002-2004}, {2006-null}].

This task is related to tracking politicians' party memberships using an API, dealing with complications when they switch parties or rejoin previous ones.

interface YearRange{
    start: number
    end: number
}

function analyzeYearGaps(yearRanges: any[]) {
        let startingYears: number[] = [];
        let endingYears: (number | null)[] = [];
        let timePeriods: (number | null)[] = [];
    
        for (let y of yearRanges) {
            startingYears.push(y.start);
            endingYears.push(y.end);
        }
    
        let matchedEndingYears: (number | null)[] = [];
        let unmatchedEndingYears: (number | null)[] = [];
        for (let e of endingYears) {
            const found = startingYears.find((s) => s == e);
            if (found) {
                matchedEndingYears.push(found);
            } else if (!found) {
                unmatchedEndingYears.push(e);
            }
        }
    
        if (unmatchedEndingYears.length > 0) {
            let start: number;
            for (let e of unmatchedEndingYears) {
                let lesser: number[] = [];
                if (e == null) {
                    if (startingYears.length > 1) {
                        start = Math.min(...startingYears);
                    } else {
                        start = startingYears[0];
                    }
                } else if (e != null) {
                    lesser = startingYears.filter((s) => s < e);
                    start = Math.max(...lesser);
                }
    
                timePeriods.push({ start: start, end: e });
            }
        } else if (unmatchedEndingYears.length == 0) {
            let start: number = Math.min(...startingYears);
            let end: number | null;
            if (endingYears.includes(null)) {
                end = null;
            } else {
                end = Math.max(...endingYears);
            }
            timePeriods.push({ start: start, end: end });
        }
        console.log(timePeriods);
        return timePeriods;
    }

Answer №1

Breaking down your code into smaller functions would enhance readability and facilitate debugging.

UPDATE with reduce Method O(N)

Upon reviewing @yogi's feedback, a more elegant solution involves sorting the array initially with complexity O(N).

function mergeRange(ranges) {
    ranges.sort((r1,r2)=> r1.start-r2.start);
    return ranges.reduce((result, current) => {
        if (result.length === 0) return [current];
        const lastRange = result[result.length - 1];
        if (lastRange.end === NOW || lastRange.end >= current.start) {
            lastRange.end = (lastRange.end === NOW || current.end === NOW) ? NOW : Math.max(lastRange.end, current.end)
        } else {
            result.push(current);
        }
        return result;
    }, []);
}

ORIGINAL APPROACH O(N2)

This method outlines building the result list by sequentially adding range:

  • If the range does not overlap with any other range in the result list, it's added without merging
  • If the range overlaps with one or multiple ranges from the results list, they are merged
const indexRanges = [{start: 2002, end: 2004}, {start: 2003, end: 2006},{start: 2007, end: 2008}, {start: 2008, end: null}]
const NOW = null;

console.log(mergeRange(indexRanges));

function mergeRange(ranges) {
  let resultRangeList = [];
  for (let range of ranges) {
    // Ranges from resultRangeList that do not overlap with current range
    const nonOverlappingRanges = [];
    // Initialized mergedRange to current range
    let mergedRange = range;
    resultRangeList.forEach((existingRange) => {
        if (hasOverlap(existingRange, mergedRange)) {
            // If merged range overlaps with an existing range, merge them and update mergedRange
            mergedRange = getMergedRange(existingRange, mergedRange);
        } else {
            // Otherwise, add existing range to non-overlapping ranges
            nonOverlappingRanges.push(existingRange);
        }
    })
    // Update result list with non-overlapping ranges and new merged range
    resultRangeList = [...nonOverlappingRanges, mergedRange];
  }
  // Return sorted result list from earliest to oldest dates
  return resultRangeList.sort((r1, r2) => r1.start - r2.start);
}

// Determine merged range when given two overlapping ranges
function getMergedRange(range1, range2) {
    const start = Math.min(range1.start, range2.start);
    const end = (range1.end === NOW || range2.end === NOW) ? NOW : Math.max(range1.end, range2.end);
    return {start, end};
}

// Check if two ranges overlap
function hasOverlap(range1, range2) {
    return isWithinRange(range1, range2.start) || isWithinRange(range1, range2.end);
}

// Verify if a year falls within a specific range
function isWithinRange(range, aYear) {
    const start = num(range.start);
    const end = num(range.end);
    const year = num(aYear);
    return start <= year && year <= end;
}

// Convenience function to convert all years to numbers easily (especially for cases where end year is null)
function num(year) {
    return year === NOW ? Number.MAX_VALUE : year;
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Sort an array according to the positions specified in another array

I am working with 2 arrays First Array : $agents = Array('abc','xyz','pqr'); Second Array : $tot_calls = Array ('10','5','20'); In the second array, each element reflects the total calls mad ...

How can you prevent an element from overflowing when employing window.pageYOffset and using a switch case to alter the background color at specified pixel thresholds?

I want the <body> element's background-color to change every 500px as I scroll down. I've scoured the web for answers, but still can't figure out what's going wrong. Please review the code. However, Firefox Browser indicates that ...

When transitioning between views in Angular App, it freezes due to the large data response from an HTTP request

I am encountering an issue with my Angular 9.1.11 application where it freezes after navigating from one page to another (which belongs to a different module with lazy loading). Here is the scenario: There is an action button called View Report that re ...

Check to see if a guest has shown support or followed an outside party

When you already follow a company on Twitter, the "Follow Us" button of that company will automatically turn grey, regardless of the domain. So, how can you determine if User-X is following companies A, B, and/or C based on their Twitter handles? The same ...

Ways to determine if an HTML element contains a child element that is a hyperlink

I need to verify the presence of an object on the page that contains a link. The object is represented as follows: <td > <input class="ng" type="checkbox"/> <a href="http://testsite.com ">67365853</a> </td> Although ...

The resource was treated as an image but sent with the MIME type application/octet-stream

Upon accessing my webpage, a warning message is displayed: Resource interpreted as Image but transferred with MIME type application/octet-stream The images on my page are in JPEG format. The server writes the image bytes to an output stream and sends it ...

Is there a way to assign a role to a user without requiring them to send a message beforehand?

I've been searching for a solution to this issue, but all I could find were instructions on how to assign a server role to someone who has interacted in some way. Is there a way to locate a specific user on a server and assign a role to them without ...

Steps to reset default behavior after using event.preventDefault()

I came across a similar question on this post, but the solution provided did not work for my specific needs. So, I decided to share some example code and provide a bit of explanation... $(document).keypress( function (event) { // Pressing Up o ...

Display JSON information in a table using AngularJS

As I delve back into an old project, I've encountered a hurdle. My goal is to display some data in a table, but I seem to have forgotten the intricacies of working with JSON objects and Angular. The API response I'm receiving looks something lik ...

What could be causing the error in Angular 2 when using multiple conditions with ng-if?

My aim is to validate if the length of events is 0 and the length of the term is greater than 2 using the code below: <li class="more-result" *ngIf="events?.length == 0 && term.value.length > 2"> <span class="tab-content- ...

jQuery UI Error: e.widget.extend cannot be used as a function

Recently, I made some changes to my jQuery files which now include jQUery UI for using the tooltip feature. However, I am facing an issue where Javascript is throwing the following error: TypeError: e.widget.extend is not a function Can someone provide ...

There was an issue with the NextJS axios request as it returned a status code

I'm currently in the process of developing an application with NextJS and Strapi In my project, I am fetching data from Strapi using Axios within NextJS Next: 14.0.4 Axios: ^1.6.5 Strapi: 4.17.1 Node: 18.17.0 Here is the code snippet: import axios f ...

Applying a consistent script with varying inputs on the same HTML page

Is it possible to create a JavaScript code that can be used across different sections of an HTML document? The goal is for the script to fetch data such as title, runtime, and plot from a specific URL request and insert this information into the appropriat ...

Creating an Array in AngularJS with ng-model and submitting it with jQuery: A comprehensive guide

I am struggling to submit an array of values using jQuery and AngularJS. Whenever I click the submit button, only the first array value is being retrieved. How can I get all array values using ng-model? Here is a link to all my code: https://jsfiddle.net/r ...

Angular.js model that is updated by checkboxes

In my project, I have created a model that is linked to several other models. For instance, let's consider a scenario similar to a Stack Overflow question associated with tags. Before making a POST or PUT request, the final Object may appear like this ...

What are the Functions of Ctrl-K on Stack Overflow?

I'm intrigued by how to incorporate the Ctrl+K (code sample) feature for code. For example: public static void main(String args[]){ System.out.println.out("welcome"); } Is there a way to nicely format this? Do we need any specific package to ...

AngularJS bracket-enhanced template

Why is AngularJS giving an error when brackets are used inside ng-template content? I am trying to create an input field that should accept an array, but I keep getting this error message: "Error: Syntax Error: Token ']' not a primary expression ...

Position the center of an Angular Material icon in the center

Seeking help to perfectly center an Angular Material icon inside a rectangular shape. Take a look at the image provided for reference. The current positioning appears centered, but upon closer inspection, it seems slightly off-center. It appears that the ...

What are the steps to implement the jQuery slide menu effect on a website?

When visiting the website , you may notice a symbol positioned in the top left corner of the site. By clicking on this symbol, a sleek div will slide out. How can this type of animation be achieved through javascript or jquery? ...

Error: Next.js is throwing a SyntaxError due to encountering an unexpected token 'export'

I encountered an issue when trying to render the following code: SyntaxError: Unexpected token 'export' (project path)/node_modules/react-syntax-highlighter/dist/esm/styles/prism/index.js Everything seems to work as expected initially, but then ...