Efficiently arranging felt tip pens by organizing them in a 2D grid based on their similarity to neighboring items, implemented using JavaScript [revised]

UPDATE: Check out the latest details and code for this question below!

Note: This question focuses on optimizing the arrangement of items in a matrix, not discussing colors. Initially, I thought providing context would be helpful, but it ended up being too much color talk and not enough algorithm discussion. 😔


I have a pack of 80 unsorted felt tip pens for my child, and it's driving me crazy.

https://i.sstatic.net/dCnnY.png

I used to play Blendoku on Android where you arrange colors to form gradients with nearby colors being similar:

https://i.sstatic.net/6EHa0.gif

It's fun to organize colors like a crossword puzzle, but with these markers, I have a full 2D grid and non-uniform gradient colors.

I can't sort these pens by intuition; I need an algorithm!

Here's what I have:

  • Firm grasp on JavaScript
  • A flat array of color values for all pens
  • A function distance(color1, color2) that measures color similarity from 0 (identical) to 100

All I'm missing is an algorithm.

With 80!, brute forcing is unrealistic.

Possible approaches to make brute force workable:

  • Set positions for some pens to reduce combinations
  • Eliminate branches with highly dissimilar neighbors
  • Stop after finding decent arrangement

But I still need an actual algorithm for this challenge.

PS Homework:

  • Sorting a matrix by similarity -- unanswered
  • Algorithm for optimal 2D color palette arrangement -- no answers
  • How to sort colors in two dimensions? -- complex problem unsolved
  • Sort Colour / Color Values -- single flat array

Update

Goal

Arrange 80 predefined colors in an 8×10 grid to create smooth gradients.

No definitive solution expected due to subjective nature of color perception.

Already have function for comparing colors.

Color space is 3D

Human eye sees colors in 3D trichromatic model.

Various models describe colors in 3D: RGB, HSL, HSV, etc.

Example palette:

https://i.sstatic.net/UY6D6.png

Hue vs saturation view lacks bright and dark colors.

This view misses many colors compared to full HSL/HSV space:

https://i.sstatic.net/ujLtC.png

Impossible to display all colors in gradient tear-free 2D layout.

E.g., lexicographically ordered RGB colors exhibit tearing:

https://i.sstatic.net/qNUmJ.png

My aim is good enough arrangement with mostly similar neighbors over perfect gradient.

Optimize grid in JS, not compare colors!

Using Delta E 2000 function to gauge color similarity, tailor each item pair adjacency for minimal difference.

Focus on optimization like mathematical functions.

Paths to tackle issue:

  • genetic algorithm
  • solver library
  • manual sorting + algorithms
  • other methods

Seeking JS solutions like CPLEX or Gurobi libraries, or genetic algo challenges like mating concepts.

Code snippet and color samples

JS code template available at: Click here

Setting instructions ensure effective operation.


Source data:

const data = [
  {index: 1, id: "1", name: "Wine Red", rgb: "#A35A6E"},
  {index: 2, id: "3", name: "Rose Red", rgb: "#F3595F"},
  {index: 3, id: "4", name: "Vivid Red", rgb: "#F4565F"},
  // ...
];

Index is color sequence based on ID order.

ID represents pen manufacturer number.


Color class.

Color abstraction simplifies color comparison.

  index;
  id;
  name;
  rgbStr;
  collection;

  constructor( {index, id, name, rgb}, collection ) {
    // method implementations...
}

Collection class stores all colors and sorts them.

class Collection {
  // Data goes here

  constructor(data) {
     // method implementations...
}

console.log(collection);

collection.render("colorsLinear");

Sample output shown in image below:

https://i.sstatic.net/DuWcf.png

Answer â„–1

I was able to come up with a creative solution that achieved an objective value of 1861.54 by combining various ideas.

  1. Created unordered color clusters of size 8 by utilizing a min-cost matching approach and merging matched subclusters, repeating the process three times. The distance function used for subclusters C1 and C2 is d(C1, C2) = ∑c1 in C1 ∑c2 in C2 d(c1, c2).

  2. Determined the optimal 2 × 5 cluster arrangement based on the aforementioned distance function. This required brute forcing 10! permutations (or 10!/4 considering symmetry, which I opted not to exploit).

  3. Optimized the layout of each cluster individually by testing all 4 × 2 arrangement permutations through brute force method (potential for further symmetry breaking, although I did not pursue this).

  4. Explored all possible ways (4^10) to flip the clusters using brute force technique. (Additional symmetry breaking opportunities were available but not pursued.)

  5. Enhanced the arrangement further through local search. Implemented two types of rounds: a 2-opt round where pairs of positions are considered for swapping, and a large-neighborhood round where a random maximal independent set is selected and optimally reassigned using the Hungarian method (facilitated by the constraint that moved items cannot be adjacent).

The final output has been visualized here:

https://i.sstatic.net/Ki7O9.png

For those interested, the Python implementation can be found at https://github.com/eisenstatdavid/felt-tip-pens

Answer â„–2

To crack this puzzle, shift your focus away from seeing it as an array for a moment and instead fix your gaze on the corners.

First things first, clearly define the problem you're aiming to solve. Traditional colors operate in three dimensions: hue, saturation, and value (darkness). Attempting to map all three dimensions onto a two-dimensional grid won't work seamlessly. However, there's a workaround.

If your goal is to orderly arrange colors ranging from white to black and red to purple, tweak your distance function to interpret variations in darkness as distance along with differences in hue values (no warping!). This adjustment will yield a color sorting that aligns with a four-corner scheme.

Next, tether each of your colors to the four corners by defining (0:0) as black, (1:1) as white, (0,1) as red (0 hue), and (1:0) as purple-red (350+ hue), simulating that purple-red equals purple for simplicity:

https://i.sstatic.net/ApfWB.png

Now, you have established two extremes: darkness and hue. But here's the twist... if we rotate the box by 45 degrees...

https://i.sstatic.net/T1Aq6.png

Spot the revelation? Not yet? The X and Y axes are now aligned with our two extreme metrics! All that remains is dividing each color's distance from white by the distance of black from white, and each color's distance from purple by the distance of red from purple, thus obtaining our Y and X coordinates, respectively!

Add a few more pens to the mix:

https://i.sstatic.net/JIY6E.png

Proceed to iterate through all the pens at O(n)^2 complexity, pinpointing the nearest distance between any pen and its final position uniformly across the rotated grid. Maintain a record of these distances, replacing them if a pen's slot has been occupied. By doing so, positioning pens in their closest slots becomes achievable in polynomial time O(n)^3.

https://i.sstatic.net/jdzrS.png

But wait, there's more to be done. HSV operates in three dimensions, prompting us to incorporate this aspect into our model as well! Expand the previous algorithm by integrating a third dimension before calculating the closest distances. Insert the 2D plane into a 3D space by intersecting it with the two color extremes and the horizontal line between white and black. This can be accomplished by locating the midpoint of the two color extremes and adjusting darkness slightly. Subsequently, distribute our pen slots uniformly on this plane and position our pens directly in this 3D space based on their HSV values - H corresponding to X, V representing Y, and S for Z.

https://i.sstatic.net/J0y63.png

With the inclusion of saturation in the 3D representation of the pens, resume iterating over the pen positions to determine the nearest one for each within polynomial time.

Tada! Your pens are now neatly organized. Should you desire the outcome in an array format, evenly generate coordinates for each array index and proceed sequentially!

Shift focus from pen sorting and delve into crafting code!

Answer â„–3

In the comment section, it was brought to your attention that you appear to be interested in locating one of the global minima for a discrete optimization problem. If this is a new concept for you, it might be beneficial to delve deeper into the topic.

Consider an error (objective) function that calculates the sum of distances between all pairs of adjacent pens. An optimal solution (pen arrangement) minimizes this error function. There may exist multiple optimal solutions, and it's important to note that different error functions can yield different results beyond the simplistic example I just provided.

You could utilize a pre-made optimizer tool (like CPLEX or Gurobi) by supplying it with a valid formulation of your problem. It may discover an optimal solution, but even if not, it might present a satisfactory sub-optimal solution.

An alternative approach would be to develop your own heuristic algorithm (e.g., a specialized genetic algorithm) to potentially surpass what the solver could achieve within its time and space constraints. Given your resources including input data, a function for assessing color dissimilarity, and JavaScript skills, crafting a heuristic algorithm is likely the more comfortable route for you.


This response initially lacked code samples as real-world problems like this usually don't have simple one-size-fits-all solutions.

The process of performing these calculations using JavaScript, especially on a web browser, may seem unconventional. Yet, at the request of the author, here's a JavaScript implementation of a basic evolutionary algorithm hosted on CodePen.

Due to the increased input size compared to the 5x5 demo originally presented, along with prolonged algorithmic iterations and slow code execution, the runtime is extensive. Although adjustments were made to avoid excessive re-calculation due to mutations, running the program still requires substantial time. The provided solution required approximately 45 minutes to execute via CodePen's debug mode.

https://i.sstatic.net/lyZ3v.png

This solution yielded an objective function just below 2060 and was generated utilizing specific parameters:

const SelectionSize = 100;
const MutationsFromSolution = 50;
const MutationCount = 5;
const MaximumGenerationsWithoutImprovement = 5;

It should be noted that slight modifications to parameters can lead to significant changes in the algorithm's outcomes. Increasing mutation count or selection size enhances program duration but can also result in superior results. Experimenting with various parameters is advised to seek better solutions, though they'll likely demand additional computational time.

Oftentimes, optimal enhancements derive from algorithmic alterations rather than sheer computing power. Innovative strategies concerning mutations and recombinations are often key to achieving improved results when employing a genetic algorithm.

Utilizing an explicitly seeded and reproducible pseudo-random number generator (PRNG) instead of Math.random() enables repeated program runs for debugging and reproducibility proof.

Considering setting up a visualization mechanism for monitoring the algorithm's progress (beyond mere console.log outputs) offers insight into the process rather than solely focusing on the final result.

Allowing human intervention (to propose mutations and guide the search based on personal color perception) can aid in attainments desired. This introduces an Interactive Genetic Algorithm (IGA). For further exploration, refer to the publication J. C. Quiroz, S. J. Louis, A. Shankar and S. M. Dascalu, "Interactive Genetic Algorithms for User Interface Design," 2007 IEEE Congress on Evolutionary Computation, Singapore, 2007, pp. 1366-1373, doi: 10.1109/CEC.2007.4424630..

Answer â„–4

If a total ordering function could be created to determine the 'darker' color between two colors, it would be possible to sort an array of colors from dark to light (or vice versa) using this function.

Starting at the top left with the first color in the sorted array, continue diagonally across the grid while filling in subsequent elements. This method results in a gradient-filled rectangular grid where neighboring colors are similar.

https://i.sstatic.net/hiYBe.png

Do you believe this approach aligns with your goal?

You have the option to alter the appearance by adjusting the behavior of the total ordering function. For instance, when arranging colors based on similarity using a color map depicted below, the total ordering can be defined as a traversal from one cell to the next. Through modifying which cell is selected next in the traversal process, various color-similar gradient grid fills can be achieved.

https://i.sstatic.net/5nmn3.png

Answer â„–5

There appears to be a possible simple solution for this issue by calculating the average color surrounding each color and placing it in that approximate position. For example:

C[j] ~ sum_{i=1...8}(C[i])/8

This is known as the discrete Laplace operator, where solving this equation means defining a discrete harmonic function over the color vector space. Harmonic functions adhere to the mean-value property, which asserts that the average value of the function in a neighborhood is equal to its center value.

To find a specific solution, we must establish boundary conditions by fixing at least two colors on the grid. Selecting 4 extreme colors and placing them in the corners of the grid seems suitable in this scenario.

One method to solve Laplace's equation effectively is through the relaxation technique, solving one linear equation at a time. While not directly applicable due to the combinatorial nature of the problem, utilizing relaxation may still yield results.

The process involves fixing the corner colors, filling the grid with their bilinear interpolation, then iteratively replacing each color C_j with the closest Laplacian color L_j from the neighboring colors until convergence criteria are met.

Guidelines for finding the closest color include proximity based on Euclidean metric and distinctiveness from neighbor colors. This operation acts as a projection into the input set of colors.

Convergence might not be achieved strictly; thus, limiting iterations to around 10 times the number of cells in the grid is reasonable. Singularities like repeated or unplaced colors should be addressed individually for optimal results.

Selecting initial corner colors could involve identifying the most diverse colors using Euclidean metrics or performing PCA on the point cloud to determine extremal points for boundary conditions.

Preliminary implementation shows promising visual results, albeit with notable singularities. Further refinement and optimization may lead to a more accurate approximation, especially when dealing with smoothly varying input colors.

UPDATE:

I have begun implementing these ideas and provided a visual comparison below. Despite some issues with singularities, the overall approach shows potential. Feel free to reach out if you find the results beneficial, and I can consider adapting the code to JavaScript.

https://i.sstatic.net/uqOky.png

Answer â„–6

In order to effectively organize a grid of colors, I propose implementing a system of color regions. These regions would consist of groups of colors where the distance between points is within a certain tolerance. Within each region, the central point would be determined as the closest to all other points in terms of average distance.

Initially, my algorithm would analyze the grid of colors and identify clusters that could form cohesive color regions. The challenge then lies in determining how these regions interact with one another. Due to the structured nature of a region and the sharp contrast at its edges, there must be a consideration for interregion compatibility. It may be beneficial for two regions to align along specific sides rather than others, which could necessitate rotating one or both regions to achieve optimal positioning.

The final step involves assessing the boundaries between regions after any necessary rotations have been made. Adjustments may need to be made to ensure cohesion between neighboring regions.

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

Look for a regular expression that matches numbers ranging from 1 to 31, either with or without a leading

I am in the process of setting up validation on an <input> element to prevent the user from inputting incorrect characters. Currently, I am utilizing ng-pattern for this purpose, which successfully restricts the user from entering invalid characters. ...

What sets apart the following: ( import React from "react"; ) and ( import React from 'react'; )?

When it comes to imports, is there a distinction between using single quotes (') versus double quotes ("), for example in import React from 'react'; and import React from "react";? Are there any notable differences? ...

What is the most effective method for transferring items between arrays in JavaScript?

In my situation, I am dealing with two arrays - 'objects' and 'appliedObjects'. My goal is to find an elegant solution in Javascript and/or Angular for transferring objects from one array to another. Initially, my approach was as follo ...

Encountered an issue when trying to invoke an Asp.net method from Javascript

Is it possible to call an Asp.Net function from Javascript? I have a code sample that calls an Asp.net MVC function in this way: @{ var payUrl = "/recordings/recording-123.wav"; <!-- saves the wav file to local folder called recordings using a ...

Separate an array into equal parts according to integer values in JavaScript

Although I have not come across any questions that address my specific issue, it may be a duplicate. Imagine having an array similar to this: var hundred = [1,2,3,4,5...100] This particular array consists of 100 elements, ranging from 1 to 100. Given ...

Struggling to differentiate between Node.js and Angular

After a lot of reading and attempts to integrate NodeJS & Angular, I'm still struggling to make it work. I have set up the server.js, mainController, and index.html. The server is running fine but Angular seems to be giving me trouble. It has been one ...

"Unexpected behavior observed as array elements in C language change their values on their own within a

I've been working on some code in C and encountered a strange issue. Despite not having any command to change the element values, they seem to be changing during the loop. Specifically, all elements calculated in the first loop end up as zero once the ...

Dropdown with multiple selections organized in a hierarchical structure

I am in need of the following : Implement a drop-down menu that reflects hierarchical parent-child relationships. Include a checkbox for each node to allow for selection. If all child nodes are selected, their parent node should be automatically ch ...

Tips for designing a Quasar page that dynamically loads content as the user scrolls

I am attempting to develop a component that will render multiple elements as the page is scrolled. I am utilizing the Quasar framework for Vue.js. Below is the code required to render a single block containing information from a JSON file: Quasar comes wi ...

What is the significance of having 8 pending specs in E2E Protractor tests on Firefox?

Each time I execute my tests, the following results are displayed: There were 11 specs tested with 0 failures and there are 8 pending specs. The test execution took 56.861 seconds to complete. [launcher] There are no instances of WebDriver still running ...

Guidelines for grasping the MNIST Binary converter in c++?

Recently, I've been working on converting the mnist data-set to images and labels. It is a binary file with a specific structure which can be found at this link. Being a fan of c++ programming, I decided to explore input/output in binary using c++. Du ...

Redirect to the homepage using HTML, CSS, and JavaScript by modifying the pathname

I have my own code editor called , developed using HTML/CSS/JS only. Currently, I am working on adding a new feature similar to what is offered by . The new feature involves redirecting users who visit to a randomly generated code along with the domain ...

Store the arrays in a JSON file before utilizing Array.push() to append data into it

I'm currently developing a calendar software and I want to store events in a JSON file. My strategy involves nesting arrays within arrays in the JSON format, allowing for easy iteration and loading during program initialization. My main question is: ...

Troubles with IE7 regarding jQuery [clicking and changing events]

My code snippets are designed to loop through cached JSON data on the 1st click function and display any values that exist for a specific id. The 2nd change function captures when elements' values change (e.g., from yes to no). Since these elements a ...

Guide on invoking the server-side method in Office 365 using JavaScript files

Exploring the capabilities of office 365 API apps and looking for documentation on how to access specific row(s) and cell(s) values / select a particular sheet programmatically in a .js file. Currently, I am utilizing the following code within a function: ...

The presence of a default value within an Angular ControlValueAccessor triggers the dirty state due to

My task is to create dynamic Input components for a template driven form using a directive. The default value of the Input component should be set by the component itself. However, I encountered an issue where setting a default value automatically marks t ...

Ways to optimize memory usage and eliminate Vue reactivity

I've encountered an issue where Vue's observer and reactive components are using up a significant amount of memory during runtime. You can see an example of this in the Memory allocation on DevTools. Is there a way to detach observables and preve ...

JavaScript Array failing to transfer to PHP using AJAX

I've encountered a recurring issue with my code and despite searching for solutions, I can't seem to find one that works for me. The problem lies in trying to delete a specific row from a table based on whether the user selects a checkbox associa ...

Moving from hosting an Angular application with "ng serve" to hosting it with Node.js involves making some adjustments to your development environment. Let's explore

After creating my first Angular2 application and hosting it with `ng serve`, I realized that I needed to add some backend functionality for server logic. Searching online, I found a helpful article on hosting an Angular 2 app on NodeJs. However, I was used ...

Attempting to alter an image with a click and then revert it back to its original state

I'm currently working on a feature that toggles an image when a specific class is clicked. The code I have so far successfully switches from 'plus.png' to 'minus.png' upon clicking, but I need it to switch back to 'plus.png&ap ...