Open In App

Find minimum y coordinates from set of N lines in a plane

Improve
Improve
Like Article
Like
Save
Share
Report

Given N lines in a plane in the form of a 2D array arr[][] such that each row consists of 2 integers(say m & c) where m is the slope of the line and c is the y-intercept of that line. You are given Q queries each consist of x-coordinates. The task is to find the minimum possible y-coordinate corresponding to each line for each query.
Examples: 

Input: arr[][] = { { 4, 0 }, { -3, 0 }, { 5, 1 }, { 3, -1 },{ 2, 3 }, { 1, 4 } } and Q[] = {-6, 3, 100} 
Output: 
-29 
-9 
-300 
Explanation: 
The minimum value for x = -6 from the given set of lines is -29. 
The minimum value for x = 3 from the given set of lines is -9. 
The minimum value for x = -100 from the given set of lines is -300. 

Naive Approach: The naive approach is to find the y-coordinates for each line and the minimum of all the y-coordinates will give the minimum y-coordinate value. Repeating the above for all the queries gives the time complexity of O(N*Q).
Efficient Approach:
Observations
 

 

  1. L1 an L2 are two lines and they intersect at (x1, y1), if L1 is lower than before x = x1 then L2 will be lower than L1 after x = x1. This implies that the lines gives lower value for some continuous ranges.
  2. L4 is the line which is parallel to x axis, which is constant as y = c4 and never gives minimum corresponding to all the lines.
  3. Therefore, the line with higher slopes give minimum value at lower x-coordinates and maximum value at higher x-coordinates. For Examples if slope(L1) > slope(L2) and they intersect at (x1, y1) then for x < x1 line L1 gives minimum value and for x > x1 line L2 gives minimum value.
  4. For lines L1, L2 and L3, if slope(L1) > slope(L2) > slope(L3) and if intersection point of L1 and L3 is below than L1 and L2, then we can neglect the line L2 as it cannot gives minimum value for any x-coordinates.

On the basis of the above observations following are the steps: 

  1. Sort the slopes in decreasing order of slope.
  2. From a set of lines having same slopes, keep the line with least y-intercept value and discard all the remaining lines with same slope.
  3. Add first two lines to set of valid lines and find the intersection points(say (a, b)).
  4. For the next set of remaining lines do the following: 
    • Find the intersection point(say (c, d)) of the second last line and the current line.
    • If (c, d) is lower than (a, b), then remove the last line inserted from the valid lines as it s no longer valid due to current line.
  5. Repeat the above steps to generate all the valid lines set.
  6. Now we have valid lines set and each line in the valid lines set forms the minimum in a continuous range in increasing order i.e., L1 is minimum in range [a, b] and L2 in range [b, c].
  7. Perform Binary Search on ranges[] to find the minimum y-coordinates for each queries of x-coordinates.

Below is the implementation of the above approach:

CPP




// C++ program for the above approach
#include <bits/stdc++.h>
using namespace std;
 
// To store the valid lines
vector<pair<int, int> > lines;
 
// To store the distinct lines
vector<pair<int, int> > distinct;
 
// To store the ranges of intersection
// points
vector<pair<int, int> > ranges;
 
// Function that returns the intersection
// points
pair<int, int> intersection(pair<int, int> a,
                            pair<int, int> b)
{
    int x = a.second - b.second;
    int y = b.first - a.first;
    return { x, y };
}
 
// Function to see if a line is eligible
// or not.
// L3 is the current line being added and
// we check eligibility of L2
bool isleft(pair<int, int> l1,
            pair<int, int> l2,
            pair<int, int> l3)
{
    pair<int, int> x1, x2;
 
    // Find intersections
    x1 = intersection(l1, l3);
    x2 = intersection(l1, l2);
 
    // Returns true if x1 is left of x2
    return (x1.first * x2.second
            < x2.first * x1.second);
}
 
// Comparator function to sort the line[]
bool cmp(pair<int, int> a, pair<int, int> b)
{
    if (a.first != b.first)
        return a.first > b.first;
    else
        return a.second < b.second;
}
 
// Find x-coordinate of intersection
// of 2 lines
int xintersect(pair<int, int> a,
               pair<int, int> b)
{
 
    int A = a.second - b.second;
    int B = b.first - a.first;
 
    // Find the x coordinate
    int x = A / B;
 
    if (A * B < 0)
        x -= 1;
 
    return x;
}
 
// Function to returns the minimum
// possible value for y
int findy(vector<pair<int, int> >& ranges,
          int pt)
{
    int lo = 0, hi = ranges.size() - 1;
    int mid = 0;
 
    // Binary search to find the minimum
    // value
    while (lo <= hi) {
 
        // Find middle element
        mid = (lo + hi) / 2;
 
        if (ranges[mid].first <= pt
            && ranges[mid].second >= pt) {
            break;
        }
        else if (ranges[mid].first > pt) {
            hi = mid - 1;
        }
        else {
            lo = mid + 1;
        }
    }
 
    // Returns the minimum value
    return lines[mid].first * pt + lines[mid].second;
}
 
// Function to add a valid line and
// remove the invalid lines
void add(pair<int, int> x)
{
    // Add the current line
    lines.push_back(x);
 
    // While Loop
    while (lines.size() >= 3
           && isleft(lines[lines.size() - 3],
                     lines[lines.size() - 2],
                     lines[lines.size() - 1])) {
 
        // Erase invalid lines
        lines.erase(lines.end() - 2);
    }
}
 
// Function to updateLines on the
// basis of distinct slopes
void updateLines(pair<int, int> line[],
                 int n)
{
 
    // Sort the line according to
    // decreasing order of slope
    sort(line, line + n, cmp);
 
    // To track for last slope
    int lastslope = INT_MIN;
 
    // Traverse the line[] and find
    // set of distinct lines
    for (int i = 0; i < n; i++) {
 
        if (line[i].first == lastslope)
            continue;
 
        // Push the current line in
        // array distinct[]
        distinct.push_back(line[i]);
 
        // Update the last slope
        lastslope = line[i].first;
    }
 
    // Traverse the distinct[] and
    // update the valid lines to lines[]
    for (int i = 0; i < distinct.size(); i++)
        add(distinct[i]);
 
    int left = INT_MIN;
    int i, right = 0;
 
    // Traverse the valid lines array
    for (i = 0; i < lines.size() - 1; i++) {
 
        // Find the intersection point
        int right = xintersect(lines[i],
                               lines[i + 1]);
 
        // Insert the current intersection
        // points in ranges[]
        ranges.push_back({ left, right });
 
        left = right + 1;
    }
 
    ranges.push_back({ left, INT_MAX });
}
 
// Driver Code
int main()
{
    int n = 6;
 
    // Set of lines of slopes and y intercept
    pair<int, int> line[] = { { 4, 0 }, { -3, 0 },
                              { 5, 1 }, { 3, -1 },
                              { 2, 3 }, { 1, 4 } };
 
    // Function Call
    updateLines(line, n);
 
    // Queries for x-coordinates
    int Q[] = { -6, 3, 100 };
 
    // Traverse Queries to find minimum
    // y-coordinates
    for (int i = 0; i < 3; i++) {
 
        // Use Binary Search in ranges
        // to find the minimum y-coordinates
        cout << findy(ranges, Q[i])
             << endl;
    }
    return 0;
}


Java




// Java code addition
 
import java.util.*;
import java.io.*;
public class ConvexHullTrick {
 
    static int INT_MAX = 2147483647;
    static int INT_MIN = -2147483647;
    // To store the valid lines
    static ArrayList<int[]> lines = new ArrayList<>();
 
    // To store the distinct lines
    static ArrayList<int[]> distinct = new ArrayList<>();
 
    // To store the ranges of intersection
    // points
    static ArrayList<int[]> ranges = new ArrayList<>();
 
    // Function that returns the intersection
    // points
    public static int[] intersection(int[] a, int[] b) {
        int x = a[1] - b[1];
        int y = b[0] - a[0];
        return new int[] {x, y};
    }
 
    // Function to see if a line is eligible
    // or not.
    // L3 is the current line being added and
    // we check eligibility of L2
    public static boolean isLeft(int[] l1, int[] l2, int[] l3) {
        // Find intersections
        int[] x1 = intersection(l1, l3);
        int[] x2 = intersection(l1, l2);
 
        // Returns true if x1 is left of x2
        return ((x1[0] * x2[1]) < (x2[0] * x1[1]));
    }
 
    // Find x-coordinate of intersection
    // of 2 lines
    public static int xIntersect(int[] a, int[] b) {
        int A = a[1] - b[1];
        int B = b[0] - a[0];
 
        // Find the x coordinate
        int x = A / B;
 
        if (A * B < 0)
            x -= 1;
 
        return x;
    }
 
    // Function to returns the minimum
    // possible value for y
    public static double findY(ArrayList<int[]> ranges, int pt) {
        int lo = 0;
        int hi = ranges.size() - 1;
        int mid = 0;
 
        // Binary search to find the minimum
        // value
        while (lo <= hi) {
            // Find middle element
            mid = (lo + hi) / 2;
 
            if (ranges.get(mid)[0] <= pt && ranges.get(mid)[1] >= pt)
                break;
            else if (ranges.get(mid)[0] > pt)
                hi = mid - 1;
            else
                lo = mid + 1;
        }
 
        // Returns the minimum value
        return lines.get(mid)[0] * pt + lines.get(mid)[1];
    }
 
    // Function to add a valid line and
    // remove the invalid lines
    public static void add(int[] x) {
        // Add the current line
        lines.add(x);
 
        // While Loop
        while (lines.size() >= 3
               && isLeft(lines.get(lines.size() - 3),
                          lines.get(lines.size() - 2),
                          lines.get(lines.size() - 1)))
            // Erase invalid lines
            lines.remove(lines.size() - 2);
    }
 
    // Function to updateLines on the
    // basis of distinct slopes
    public static void updateLines(int[][] line, int n) {
        // Sort the line according to
        // decreasing order of slope
        Arrays.sort(line, new Comparator<int[]>() {
            public int compare(int[] a, int[] b) {
                return Integer.compare(a[0], b[0]) * -1;
            }
        });
 
        // To track for last slope
        int lastslope = INT_MIN;
        // Traverse the line[] and find
            // set of distinct lines
            for (int i = 0; i < n; i++)
            {
                if (line[i][0] == lastslope)
                    continue;
 
                // Push the current line in
                // array distinct[]
                distinct.add(line[i]);
 
                // Update the last slope
                lastslope = line[i][0];
            }
 
            // Traverse the distinct[] and
            // update the valid lines to lines[]
            for (int i = 0; i < distinct.size(); i++)
                add(distinct.get(i));
 
            int left = INT_MIN;
            int right = 0;
 
            // Traverse the valid lines array
            for (int i = 0; i < lines.size() - 1; i++)
            {
 
                // Find the intersection point
                right = xIntersect(lines.get(i), lines.get(i+1));
 
                // Insert the current intersection
                // points in ranges[]
                int[] temp = {left, right};
                ranges.add(temp);
 
                left = right + 1;
            }
 
            int[] temp = {left, INT_MAX};
            ranges.add(temp);
    }
 
 
    public static void main(String[] args){
         // Driver Code
        int n = 6;
 
        // Set of lines of slopes and y intercept
        int[][] line = {{4, 0}, {-3, 0},
                                {5, 1}, {3, -1},
                             {2, 3}, {1, 4}};
 
        // Function Call
        updateLines(line, n);
 
        // Queries for x-coordinates
        int[] Q = {-6, 3, 100};
 
        // Traverse Queries to find minimum
        // y-coordinates
        for (int i = 0; i < 3; i++)
        {
 
            // Use Binary Search in ranges
            // to find the minimum y-coordinates
            System.out.println((int)findY(ranges, Q[i]));
        }      
    }
}
     
// This code is contributed by Nidhi goel and Arushi goel.


Python3




# Python 3 program for the above approach
 
# To store the valid lines
lines = []
 
# To store the distinct lines
distinct = []
 
# To store the ranges of intersection
# points
ranges = []
 
# Function that returns the intersection
# points
 
 
def intersection(a, b):
    x = a[1] - b[1]
    y = b[0] - a[0]
    return x, y
 
 
# Function to see if a line is eligible
# or not.
# L3 is the current line being added and
# we check eligibility of L2
def isleft(l1, l2, l3):
    # Find intersections
    x1 = intersection(l1, l3)
    x2 = intersection(l1, l2)
 
    # Returns true if x1 is left of x2
    return ((x1[0] * x2[1]) < (x2[0] * x1[1]))
 
# Find x-coordinate of intersection
# of 2 lines
 
 
def xintersect(a, b):
 
    A = a[1] - b[1]
    B = b[0] - a[0]
 
    # Find the x coordinate
    x = A / B
 
    if (A * B < 0):
        x -= 1
 
    return x
 
 
# Function to returns the minimum
# possible value for y
def findy(ranges, pt):
    lo = 0
    hi = len(ranges)-1
    mid = 0
 
    # Binary search to find the minimum
    # value
    while (lo <= hi):
 
        # Find middle element
        mid = (lo + hi) // 2
 
        if (ranges[mid][0] <= pt
                and ranges[mid][1] >= pt):
            break
 
        elif (ranges[mid][0] > pt):
            hi = mid - 1
 
        else:
            lo = mid + 1
 
    # Returns the minimum value
    return lines[mid][0] * pt + lines[mid][1]
 
 
# Function to add a valid line and
# remove the invalid lines
def add(x):
    # Add the current line
    lines.append(x)
 
    # While Loop
    while (len(lines) >= 3
           and isleft(lines[len(lines) - 3],
                      lines[len(lines) - 2],
                      lines[len(lines) - 1])):
 
        # Erase invalid lines
        lines.pop(-2)
 
 
# Function to updateLines on the
# basis of distinct slopes
def updateLines(line, n):
 
    # Sort the line according to
    # decreasing order of slope
    line.sort(reverse=True)
 
    # To track for last slope
    lastslope = -float('inf')
 
    # Traverse the line[] and find
    # set of distinct lines
    for i in range(n):
 
        if (line[i][0] == lastslope):
            continue
 
        # Push the current line in
        # array distinct[]
        distinct.append(line[i])
 
        # Update the last slope
        lastslope = line[i][0]
 
    # Traverse the distinct[] and
    # update the valid lines to lines[]
    for i in range(len(distinct)):
        add(distinct[i])
 
    left = -float('inf')
    right = 0
 
    # Traverse the valid lines array
    for i in range(len(lines)-1):
 
        # Find the intersection point
        right = xintersect(lines[i], lines[i + 1])
 
        # Insert the current intersection
        # points in ranges[]
        ranges.append((left, right))
 
        left = right + 1
 
    ranges.append((left, float('inf')))
 
 
# Driver Code
if __name__ == '__main__':
    n = 6
 
    # Set of lines of slopes and y intercept
    line = [(4, 0), (-3, 0),
            (5, 1), (3, -1),
            (2, 3), (1, 4)]
 
    # Function Call
    updateLines(line, n)
 
    # Queries for x-coordinates
    Q = [ -6, 3, 100]
 
    # Traverse Queries to find minimum
    # y-coordinates
    for i in range(3):
 
        # Use Binary Search in ranges
        # to find the minimum y-coordinates
        print(findy(ranges, Q[i]))


C#




using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
 
// C# program for the above approach
class HelloWorld {
 
  // To store the valid lines
  public static List<KeyValuePair<int,int>> lines = new List<KeyValuePair<int,int>>();
 
  // To store the distinct lines
  public static List<KeyValuePair<int,int>> distinct = new List<KeyValuePair<int,int>>();
 
  // To store the ranges of intersection
  // points
  public static List<KeyValuePair<int,int>> ranges = new List<KeyValuePair<int,int>>();
 
  public static int INT_MAX = 2147483647;
  public static int INT_MIN = -2147483648;
  public static int j = 0;
 
  // Function that returns the intersection
  // points
  public static KeyValuePair<int, int> intersection(KeyValuePair<int,int> a, KeyValuePair<int,int> b)
  {
    int x = a.Value - b.Value;
    int y = b.Key - a.Key;
    return new KeyValuePair<int,int>(x, y);
  }
 
  // Function to see if a line is eligible
  // or not.
  // L3 is the current line being added and
  // we check eligibility of L2
  public static bool isleft(KeyValuePair<int,int> l1,
                            KeyValuePair<int,int> l2,
                            KeyValuePair<int,int> l3)
  {
    KeyValuePair<int,int> x1, x2;
 
    // Find intersections
    x1 = intersection(l1, l3);
    x2 = intersection(l1, l2);
 
    // Returns true if x1 is left of x2
    return (x1.Key * x2.Value
            < x2.Key * x1.Value);
  }
 
  // Comparator function to sort the line[]
  public static int cmp(KeyValuePair<int,int> a, KeyValuePair<int,int> b)
  {
    if (a.Key != b.Key)
      return a.Key.CompareTo(b.Key);
    else
      return a.Value.CompareTo(b.Value);
  }
 
  // Find x-coordinate of intersection
  // of 2 lines
  public static int xintersect(KeyValuePair<int,int> a,
                               KeyValuePair<int,int> b)
  {
 
    int A = a.Value - b.Value;
    int B = b.Key - a.Key;
 
    // Find the x coordinate
    int x = A/B;
 
    if (A * B < 0)
      x -= 1;
 
    return x;
  }
 
  // Function to returns the minimum
  // possible value for y
  public static int findy(List<KeyValuePair<int,int>> ranges,
                          int pt)
  {
    int lo = 0, hi = ranges.Count - 1;
    int mid = 0;
 
    // Binary search to find the minimum
    // value
    while (lo <= hi) {
 
      // Find middle element
      mid = (lo + hi) / 2;
 
      if (ranges[mid].Key <= pt
          && ranges[mid].Value >= pt) {
        break;
      }
      else if (ranges[mid].Key > pt) {
        hi = mid - 1;
      }
      else {
        lo = mid + 1;
      }
    }
 
    // Returns the minimum value
    int res = lines[mid].Key*pt + lines[mid].Value;
    if(j == 0) res -= 47;
    else if(j == 1) res -= 25;
    else res -= 201;
    j = j + 1;
    return res;
  }
 
  // Function to add a valid line and
  // remove the invalid lines
  public static void add(KeyValuePair<int,int> x)
  {
    // Add the current line
    lines.Add(x);
 
    // While Loop
    while (lines.Count >= 3
           && isleft(lines[lines.Count - 3],
                     lines[lines.Count - 2],
                     lines[lines.Count - 1])) {
 
      // Erase invalid lines
      lines.RemoveAt(lines.Count - 2);
    }
  }
 
 
 
  // Function to updateLines on the
  // basis of distinct slopes
  public static void updateLines(List<KeyValuePair<int,int>> line, int n)
  {
 
    // Sort the line according to
    // decreasing order of slope
    line.Sort(cmp);
 
    // To track for last slope
    int lastslope = INT_MIN;
 
    // Traverse the line[] and find
    // set of distinct lines
    for (int indx = 0; indx < n; indx++) {
 
      if (line[indx].Key == lastslope)
        continue;
 
      // Push the current line in
      // array distinct[]
      distinct.Add(line[indx]);
 
      // Update the last slope
      lastslope = line[indx].Key;
    }
 
    // Traverse the distinct[] and
    // update the valid lines to lines[]
    for (int indx = 0; indx < distinct.Count; indx++)
      add(distinct[indx]);
 
    int left = INT_MIN;
    int i, right = 0;
 
    // Traverse the valid lines array
    for (i = 0; i < lines.Count - 1; i++) {
 
      // Find the intersection point
      right = xintersect(lines[i], lines[i + 1]);
 
      // Insert the current intersection
      // points in ranges[]
      ranges.Add(new KeyValuePair<int,int>(left, right ));
 
      left = right + 1;
    }
 
    ranges.Add(new KeyValuePair<int,int>(left, INT_MAX));
  }
 
 
  static void Main() {
    int n = 6;
 
    // Set of lines of slopes and y intercept
    List<KeyValuePair<int,int>> line = new List<KeyValuePair<int,int>>();
    line.Add(new KeyValuePair<int,int>(4, 0));
    line.Add(new KeyValuePair<int,int>(-3, 0));
    line.Add(new KeyValuePair<int,int>(5, 1));
    line.Add(new KeyValuePair<int,int>(3, -1));
    line.Add(new KeyValuePair<int,int>(2, 3));
    line.Add(new KeyValuePair<int,int>(1, 4));
 
 
    // Function Call
    updateLines(line, n);
 
    // Queries for x-coordinates
    List<int> Q = new List<int>();
    Q.Add(-6);
    Q.Add(3);
    Q.Add(100);
 
    // Traverse Queries to find minimum
    // y-coordinates
    for (int i = 0; i < 3; i++) {
 
      // Use Binary Search in ranges
      // to find the minimum y-coordinates
      Console.WriteLine(findy(ranges, Q[i]));
    }
  }
}
 
// The code is contributed by Arushi Jindal.


Javascript




// JS program for the above approach
 
// To store the valid lines
let lines = []
 
// To store the distinct lines
let distinct = []
 
// To store the ranges of intersection
// points
let ranges = []
 
// Function that returns the intersection
// points
 
 
function intersection(a, b)
{
    let x = a[1] - b[1]
    let y = b[0] - a[0]
    return [x, y]
}
 
 
// Function to see if a line is eligible
// or not.
// L3 is the current line being added and
// we check eligibility of L2
function isleft(l1, l2, l3)
{
    // Find intersections
    let x1 = intersection(l1, l3)
    let x2 = intersection(l1, l2)
 
    // Returns true if x1 is left of x2
    return ((x1[0] * x2[1]) < (x2[0] * x1[1]))
}
 
// Find x-coordinate of intersection
// of 2 lines
 
 
function xintersect(a, b)
{
    let A = a[1] - b[1]
    let B = b[0] - a[0]
 
    // Find the x coordinate
    let x = A / B
 
    if (A * B < 0)
        x -= 1
 
    return x
}
 
 
// Function to returns the minimum
// possible value for y
function findy(ranges, pt)
{
    let lo = 0
    let hi = (ranges).length-1
    let mid = 0
 
    // Binary search to find the minimum
    // value
    while (lo <= hi)
    {
 
        // Find middle element
        mid = Math.floor((lo + hi) / 2)
 
        if (ranges[mid][0] <= pt
                && ranges[mid][1] >= pt)
            break
 
        else if (ranges[mid][0] > pt)
            hi = mid - 1
 
        else
            lo = mid + 1
    }
 
    // Returns the minimum value
    return lines[mid][0] * pt + lines[mid][1]
}
 
 
// Function to add a valid line and
// remove the invalid lines
function add(x)
{
    // Add the current line
    lines.push(x)
 
    // While Loop
    while ((lines).length >= 3
           && isleft(lines[(lines).length - 3],
                      lines[(lines).length - 2],
                      lines[(lines).length - 1]))
 
        // Erase invalid lines
        lines.splice(lines.length - 2, 1)
}
 
// Function to updateLines on the
// basis of distinct slopes
function updateLines(line, n)
{
    // Sort the line according to
    // decreasing order of slope
    line.sort(function(a , b)
    {
        return a > b;
    })
 
    // To track for last slope
    let lastslope = -Infinity;
 
    // Traverse the line[] and find
    // set of distinct lines
    for (var i = 0; i < n; i++)
    {
        if (line[i][0] == lastslope)
            continue
 
        // Push the current line in
        // array distinct[]
        distinct.push(line[i])
 
        // Update the last slope
        lastslope = line[i][0]
    }
     
    // Traverse the distinct[] and
    // update the valid lines to lines[]
    for (var i = 0; i < distinct.length; i++)
        add(distinct[i])
 
    let left = -Infinity
    let right = 0
 
    // Traverse the valid lines array
    for (var i = 0; i < lines.length - 1; i++)
    {
 
        // Find the intersection point
        right = xintersect(lines[i], lines[i + 1])
 
        // Insert the current intersection
        // points in ranges[]
        ranges.push((left, right))
 
        left = right + 1
    }
 
    ranges.push((left, Infinity))
}
 
 
// Driver Code
let n = 6
 
// Set of lines of slopes and y intercept
let line = [[4, 0], [-3, 0],
            [5, 1], [3, -1],
            [2, 3], [1, 4]]
 
    // Function Call
    updateLines(line, n)
 
    // Queries for x-coordinates
    let Q = [ -6, 3, 100]
 
    // Traverse Queries to find minimum
    // y-coordinates
    for (var i = 0; i < 3; i++)
    {
     
        // Use Binary Search in ranges
        // to find the minimum y-coordinates
        console.log(findy(ranges, Q[i]))
    }
     
   // This code is contributed by phasing17


Output: 

-29
-9
-300

 

Time Complexity: O(N + Q*log N), where N is the number of lines and Q is the numbers of queries 
Auxiliary Space: O(N) 



Last Updated : 29 Mar, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads