Export Google Spreadsheet as a JSON using script - javascript
I'm using this script to export Google Sheets as a JSON file and it works perfectly fine. However in my sheet im using names like this "qwe_qwe" and when I'm exporting its being formatted to "qweqwe". Is it possible somehow to export file in raw format.
I believe that "var FORMAT_PRETTY = 'Pretty';" makes the difference, however I can't make it work without this variable.
UPD: Found that this function if (!isAlnum_(letter)) checking for Alphanumeric chars, so I've decided to add smthng like this if (!isAlnum_(letter) && !isAlnum_("_")), however it doesn't work.
// Includes functions for exporting active sheet or all sheets as JSON object (also Python object syntax compatible).
// Tweak the makePrettyJSON_ function to customize what kind of JSON to export.
var FORMAT_ONELINE = 'One-line';
var FORMAT_MULTILINE = 'Multi-line';
var FORMAT_PRETTY = 'Pretty';
var LANGUAGE_JS = 'JavaScript';
var LANGUAGE_PYTHON = 'Python';
var STRUCTURE_LIST = 'List';
var STRUCTURE_HASH = 'Hash (keyed by "id" column)';
/* Defaults for this particular spreadsheet, change as desired */
var DEFAULT_FORMAT = FORMAT_PRETTY;
var DEFAULT_LANGUAGE = LANGUAGE_JS;
var DEFAULT_STRUCTURE = STRUCTURE_LIST;
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [
{name: "Export JSON for this sheet", functionName: "exportSheet"},
{name: "Export JSON for all sheets", functionName: "exportAllSheets"}
];
ss.addMenu("Export JSON", menuEntries);
}
function makeLabel(app, text, id) {
var lb = app.createLabel(text);
if (id) lb.setId(id);
return lb;
}
function makeListBox(app, name, items) {
var listBox = app.createListBox().setId(name).setName(name);
listBox.setVisibleItemCount(1);
var cache = CacheService.getPublicCache();
var selectedValue = cache.get(name);
Logger.log(selectedValue);
for (var i = 0; i < items.length; i++) {
listBox.addItem(items[i]);
if (items[1] == selectedValue) {
listBox.setSelectedIndex(i);
}
}
return listBox;
}
function makeButton(app, parent, name, callback) {
var button = app.createButton(name);
app.add(button);
var handler = app.createServerClickHandler(callback).addCallbackElement(parent);;
button.addClickHandler(handler);
return button;
}
function makeTextBox(app, name) {
var textArea = app.createTextArea().setWidth('100%').setHeight('200px').setId(name).setName(name);
return textArea;
}
function exportAllSheets(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sheetsData = {};
for (var i = 0; i < sheets.length; i++) {
var sheet = sheets[i];
var rowsData = getRowsData_(sheet, getExportOptions(e));
var sheetName = sheet.getName();
sheetsData[sheetName] = rowsData;
}
var json = makeJSON_(sheetsData, getExportOptions(e));
displayText_(json);
}
function exportSheet(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var rowsData = getRowsData_(sheet, getExportOptions(e));
var json = makeJSON_(rowsData, getExportOptions(e));
displayText_(json);
}
function getExportOptions(e) {
var options = {};
options.language = e && e.parameter.language || DEFAULT_LANGUAGE;
options.format = e && e.parameter.format || DEFAULT_FORMAT;
options.structure = e && e.parameter.structure || DEFAULT_STRUCTURE;
var cache = CacheService.getPublicCache();
cache.put('language', options.language);
cache.put('format', options.format);
cache.put('structure', options.structure);
Logger.log(options);
return options;
}
function makeJSON_(object, options) {
if (options.format == FORMAT_PRETTY) {
var jsonString = JSON.stringify(object, null, 4);
} else if (options.format == FORMAT_MULTILINE) {
var jsonString = Utilities.jsonStringify(object);
jsonString = jsonString.replace(/},/gi, '},\n');
jsonString = prettyJSON.replace(/":\[{"/gi, '":\n[{"');
jsonString = prettyJSON.replace(/}\],/gi, '}],\n');
} else {
var jsonString = Utilities.jsonStringify(object);
}
if (options.language == LANGUAGE_PYTHON) {
// add unicode markers
jsonString = jsonString.replace(/"([a-zA-Z]*)":\s+"/gi, '"$1": u"');
}
return jsonString;
}
function displayText_(text) {
var output = HtmlService.createHtmlOutput("<textarea style='width:100%;' rows='20'>" + text + "</textarea>");
output.setWidth(400)
output.setHeight(300);
SpreadsheetApp.getUi()
.showModalDialog(output, 'Exported JSON');
}
// getRowsData iterates row by row in the input range and returns an array of objects.
// Each object contains all the data for a given row, indexed by its normalized column name.
// Arguments:
// - sheet: the sheet object that contains the data to be processed
// - range: the exact range of cells where the data is stored
// - columnHeadersRowIndex: specifies the row number where the column names are stored.
// This argument is optional and it defaults to the row immediately above range;
// Returns an Array of objects.
function getRowsData_(sheet, options) {
var headersRange = sheet.getRange(1, 1, sheet.getFrozenRows(), sheet.getMaxColumns());
var headers = headersRange.getValues()[0];
var dataRange = sheet.getRange(sheet.getFrozenRows()+1, 1, sheet.getMaxRows(), sheet.getMaxColumns());
var objects = getObjects_(dataRange.getValues(), normalizeHeaders_(headers));
if (options.structure == STRUCTURE_HASH) {
var objectsById = {};
objects.forEach(function(object) {
objectsById[object.id] = object;
});
return objectsById;
} else {
return objects;
}
}
// getColumnsData iterates column by column in the input range and returns an array of objects.
// Each object contains all the data for a given column, indexed by its normalized row name.
// Arguments:
// - sheet: the sheet object that contains the data to be processed
// - range: the exact range of cells where the data is stored
// - rowHeadersColumnIndex: specifies the column number where the row names are stored.
// This argument is optional and it defaults to the column immediately left of the range;
// Returns an Array of objects.
function getColumnsData_(sheet, range, rowHeadersColumnIndex) {
rowHeadersColumnIndex = rowHeadersColumnIndex || range.getColumnIndex() - 1;
var headersTmp = sheet.getRange(range.getRow(), rowHeadersColumnIndex, range.getNumRows(), 1).getValues();
var headers = normalizeHeaders_(arrayTranspose_(headersTmp)[0]);
return getObjects(arrayTranspose_(range.getValues()), headers);
}
// For every row of data in data, generates an object that contains the data. Names of
// object fields are defined in keys.
// Arguments:
// - data: JavaScript 2d array
// - keys: Array of Strings that define the property names for the objects to create
function getObjects_(data, keys) {
var objects = [];
for (var i = 0; i < data.length; ++i) {
var object = {};
var hasData = false;
for (var j = 0; j < data[i].length; ++j) {
var cellData = data[i][j];
if (isCellEmpty_(cellData)) {
continue;
}
object[keys[j]] = cellData;
hasData = true;
}
if (hasData) {
objects.push(object);
}
}
return objects;
}
// Returns an Array of normalized Strings.
// Arguments:
// - headers: Array of Strings to normalize
function normalizeHeaders_(headers) {
var keys = [];
for (var i = 0; i < headers.length; ++i) {
var key = normalizeHeader_(headers[i]);
if (key.length > 0) {
keys.push(key);
}
}
return keys;
}
// Normalizes a string, by removing all alphanumeric characters and using mixed case
// to separate words. The output will always start with a lower case letter.
// This function is designed to produce JavaScript object property names.
// Arguments:
// - header: string to normalize
// Examples:
// "First Name" -> "firstName"
// "Market Cap (millions) -> "marketCapMillions
// "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored"
function normalizeHeader_(header) {
var key = "";
var upperCase = false;
for (var i = 0; i < header.length; ++i) {
var letter = header[i];
if (letter == " " && key.length > 0) {
upperCase = true;
continue;
}
if (!isAlnum_(letter)) {
continue;
}
if (key.length == 0 && isDigit_(letter)) {
continue; // first character must be a letter
}
if (upperCase) {
upperCase = false;
key += letter.toUpperCase();
} else {
key += letter.toLowerCase();
}
}
return key;
}
// Returns true if the cell where cellData was read from is empty.
// Arguments:
// - cellData: string
function isCellEmpty_(cellData) {
return typeof(cellData) == "string" && cellData == "";
}
// Returns true if the character char is alphabetical, false otherwise.
function isAlnum_(char) {
return char >= 'A' && char <= 'Z' ||
char >= 'a' && char <= 'z' ||
isDigit_(char);
}
// Returns true if the character char is a digit, false otherwise.
function isDigit_(char) {
return char >= '0' && char <= '9';
}
// Given a JavaScript 2d Array, this function returns the transposed table.
// Arguments:
// - data: JavaScript 2d Array
// Returns a JavaScript 2d Array
// Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]].
function arrayTranspose_(data) {
if (data.length == 0 || data[0].length == 0) {
return null;
}
var ret = [];
for (var i = 0; i < data[0].length; ++i) {
ret.push([]);
}
for (var i = 0; i < data.length; ++i) {
for (var j = 0; j < data[i].length; ++j) {
ret[j][i] = data[i][j];
}
}
return ret;
}
Sample Sheet:
Export to JSON (based on original code):
column headers were formatted using normalizeHeader_()
Output:
5:19:32 AM Notice Execution started
5:19:34 AM Info [
{
"columna": "A2",
"columnb": "B2",
"columnc": "C2"
},
{
"columna": "A_3",
"columnb": "B_3",
"columnc": "C_3"
}
]
5:19:34 AM Notice Execution completed
Export to JSON (raw format column header):
Change var objects = getObjects_(dataRange.getValues(), normalizeHeaders_(headers)); in getRowsData_() to var objects = getObjects_(dataRange.getValues(), headers);
Output:
5:20:04 AM Notice Execution started
5:20:06 AM Info [
{
"Column_A": "A2",
"Column_B": "B2",
"Column_C": "C2"
},
{
"Column_A": "A_3",
"Column_B": "B_3",
"Column_C": "C_3"
}
]
5:20:06 AM Notice Execution completed
Export to JSON (format column header but include '_')
Use (!isAlnum_(letter) && (letter != '_')) in normalizeHeader_()
Output:
5:28:11 AM Notice Execution started
5:28:13 AM Info [
{
"column_a": "A2",
"column_b": "B2",
"column_c": "C2"
},
{
"column_a": "A_3",
"column_b": "B_3",
"column_c": "C_3"
}
]
5:28:13 AM Notice Execution completed
There is one more solution, you can just remove !isAlnum_(letter) checking or add underscore to it.
You can use this script to export google sheets data to json format
// IMPORT
/**
* Imports JSON data to your spreadsheet Ex: IMPORTJSON("http://myapisite.com","city/population")
* #param url URL of your JSON data as string
* #param xpath simplified xpath as string
* #customfunction
*/
function IMPORTJSON(url,xpath){
try{
// /rates/EUR
var res = UrlFetchApp.fetch(url);
var content = res.getContentText();
var json = JSON.parse(content);
var patharray = xpath.split("/");
//Logger.log(patharray);
for(var i=0;i<patharray.length;i++){
json = json[patharray[i]];
}
//Logger.log(typeof(json));
if(typeof(json) === "undefined"){
return "Node Not Available";
} else if(typeof(json) === "object"){
var tempArr = [];
for(var obj in json){
tempArr.push([obj,json[obj]]);
}
return tempArr;
} else if(typeof(json) !== "object") {
return json;
}
}
catch(err){
return "Error getting data";
}
}
// EXPORT
// Includes functions for exporting active sheet or all sheets as JSON object (also Python object syntax compatible).
// Tweak the makePrettyJSON_ function to customize what kind of JSON to export.
var FORMAT_ONELINE = "One-line";
var FORMAT_MULTILINE = "Multi-line";
var FORMAT_PRETTY = "Pretty";
var LANGUAGE_JS = "JavaScript";
var LANGUAGE_PYTHON = "Python";
var STRUCTURE_LIST = "List";
var STRUCTURE_HASH = 'Hash (keyed by "id" column)';
/* Defaults for this particular spreadsheet, change as desired */
var DEFAULT_FORMAT = FORMAT_PRETTY;
var DEFAULT_LANGUAGE = LANGUAGE_JS;
var DEFAULT_STRUCTURE = STRUCTURE_LIST;
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [
{ name: "Export JSON for this sheet", functionName: "exportSheet" },
{ name: "Export JSON for all sheets", functionName: "exportAllSheets" }
];
ss.addMenu("Export JSON", menuEntries);
}
function makeLabel(app, text, id) {
var lb = app.createLabel(text);
if (id) lb.setId(id);
return lb;
}
function makeListBox(app, name, items) {
var listBox = app
.createListBox()
.setId(name)
.setName(name);
listBox.setVisibleItemCount(1);
var cache = CacheService.getPublicCache();
var selectedValue = cache.get(name);
Logger.log(selectedValue);
for (var i = 0; i < items.length; i++) {
listBox.addItem(items[i]);
if (items[1] == selectedValue) {
listBox.setSelectedIndex(i);
}
}
return listBox;
}
function makeButton(app, parent, name, callback) {
var button = app.createButton(name);
app.add(button);
var handler = app
.createServerClickHandler(callback)
.addCallbackElement(parent);
button.addClickHandler(handler);
return button;
}
function makeTextBox(app, name) {
var textArea = app
.createTextArea()
.setWidth("100%")
.setHeight("200px")
.setId(name)
.setName(name);
return textArea;
}
function exportAllSheets(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sheetsData = {};
for (var i = 0; i < sheets.length; i++) {
var sheet = sheets[i];
var rowsData = getRowsData_(sheet, getExportOptions(e));
var sheetName = sheet.getName();
sheetsData[sheetName] = rowsData;
}
var json = makeJSON_(sheetsData, getExportOptions(e));
displayText_(json);
}
function exportSheet(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var rowsData = getRowsData_(sheet, getExportOptions(e));
var json = makeJSON_(rowsData, getExportOptions(e));
displayText_(json);
}
function getExportOptions(e) {
var options = {};
options.language = (e && e.parameter.language) || DEFAULT_LANGUAGE;
options.format = (e && e.parameter.format) || DEFAULT_FORMAT;
options.structure = (e && e.parameter.structure) || DEFAULT_STRUCTURE;
var cache = CacheService.getPublicCache();
cache.put("language", options.language);
cache.put("format", options.format);
cache.put("structure", options.structure);
Logger.log(options);
return options;
}
function makeJSON_(object, options) {
// START Array/Object addon
// Make arrays and objects in cells just like usual:
// ["array", "item", "one"] and {"objectItems" : ["array", "array2"] }
var obj, o;
for (var i = 0; i < object.length; i++) {
obj = object[i];
if (obj) {
for (prop in obj) {
o = obj[prop].toString();
if (
(o && (o.indexOf("{") == 0 && o.indexOf("}") == o.length - 1)) ||
(o.indexOf("[") == 0 && o.indexOf("]") == o.length - 1)
) {
object[i][prop] = JSON.parse(o);
}
}
}
}
//END Array/Object addon
if (options.format == FORMAT_PRETTY) {
var jsonString = JSON.stringify(object, null, 4);
} else if (options.format == FORMAT_MULTILINE) {
var jsonString = Utilities.jsonStringify(object);
jsonString = jsonString.replace(/},/gi, "},\n");
jsonString = prettyJSON.replace(/":\[{"/gi, '":\n[{"');
jsonString = prettyJSON.replace(/}\],/gi, "}],\n");
} else {
var jsonString = Utilities.jsonStringify(object);
}
if (options.language == LANGUAGE_PYTHON) {
// add unicode markers
jsonString = jsonString.replace(/"([a-zA-Z]*)":\s+"/gi, '"$1": u"');
}
return jsonString;
}
function displayText_(text) {
var output = HtmlService.createHtmlOutput(
"<textarea style='width:100%;' rows='20'>" + text + "</textarea>"
);
output.setWidth(400);
output.setHeight(300);
SpreadsheetApp.getUi().showModalDialog(output, "Exported JSON (Replace any \"null\" instances with null (no quotes).");
}
// getRowsData iterates row by row in the input range and returns an array of objects.
// Each object contains all the data for a given row, indexed by its normalized column name.
// Arguments:
// - sheet: the sheet object that contains the data to be processed
// - range: the exact range of cells where the data is stored
// - columnHeadersRowIndex: specifies the row number where the column names are stored.
// This argument is optional and it defaults to the row immediately above range;
// Returns an Array of objects.
function getRowsData_(sheet, options) {
var headersRange = sheet.getRange(
1,
1,
sheet.getFrozenRows(),
sheet.getMaxColumns()
);
var headers = headersRange.getValues()[0];
var dataRange = sheet.getRange(
sheet.getFrozenRows() + 1,
1,
sheet.getMaxRows(),
sheet.getMaxColumns()
);
var objects = getObjects_(dataRange.getValues(), normalizeHeaders_(headers));
if (options.structure == STRUCTURE_HASH) {
var objectsById = {};
objects.forEach(function(object) {
objectsById[object.id] = object;
});
return objectsById;
} else {
return objects;
}
}
// getColumnsData iterates column by column in the input range and returns an array of objects.
// Each object contains all the data for a given column, indexed by its normalized row name.
// Arguments:
// - sheet: the sheet object that contains the data to be processed
// - range: the exact range of cells where the data is stored
// - rowHeadersColumnIndex: specifies the column number where the row names are stored.
// This argument is optional and it defaults to the column immediately left of the range;
// Returns an Array of objects.
function getColumnsData_(sheet, range, rowHeadersColumnIndex) {
rowHeadersColumnIndex = rowHeadersColumnIndex || range.getColumnIndex() - 1;
var headersTmp = sheet
.getRange(range.getRow(), rowHeadersColumnIndex, range.getNumRows(), 1)
.getValues();
var headers = normalizeHeaders_(arrayTranspose_(headersTmp)[0]);
return getObjects(arrayTranspose_(range.getValues()), headers);
}
// For every row of data in data, generates an object that contains the data. Names of
// object fields are defined in keys.
// Arguments:
// - data: JavaScript 2d array
// - keys: Array of Strings that define the property names for the objects to create
function getObjects_(data, keys) {
var objects = [];
for (var i = 0; i < data.length; ++i) {
var object = {};
var hasData = false;
for (var j = 0; j < data[i].length; ++j) {
var cellData = data[i][j];
if (isCellEmpty_(cellData)) {
continue;
}
object[keys[j]] = cellData;
hasData = true;
}
if (hasData) {
objects.push(object);
}
}
return objects;
}
// Returns an Array of normalized Strings.
// Arguments:
// - headers: Array of Strings to normalize
function normalizeHeaders_(headers) {
var keys = [];
for (var i = 0; i < headers.length; ++i) {
var key = normalizeHeader_(headers[i]);
if (key.length > 0) {
keys.push(key);
}
}
return keys;
}
// Normalizes a string, by removing all alphanumeric characters and using mixed case
// to separate words. The output will always start with a lower case letter.
// This function is designed to produce JavaScript object property names.
// Arguments:
// - header: string to normalize
// Examples:
// "First Name" -> "firstName"
// "Market Cap (millions) -> "marketCapMillions
// "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored"
function normalizeHeader_(header) {
var key = "";
var upperCase = false;
for (var i = 0; i < header.length; ++i) {
var letter = header[i];
if (letter == " " && key.length > 0) {
upperCase = true;
continue;
}
if (!isAlnum_(letter)) {
continue;
}
if (key.length == 0 && isDigit_(letter)) {
continue; // first character must be a letter
}
if (upperCase) {
upperCase = false;
key += letter.toUpperCase();
} else {
key += letter.toLowerCase();
}
}
return key;
}
// Returns true if the cell where cellData was read from is empty.
// Arguments:
// - cellData: string
function isCellEmpty_(cellData) {
return typeof cellData == "string" && cellData == "";
}
// Returns true if the character char is alphabetical, false otherwise.
function isAlnum_(char) {
return (
(char >= "A" && char <= "Z") ||
(char >= "a" && char <= "z") ||
isDigit_(char)
);
}
// Returns true if the character char is a digit, false otherwise.
function isDigit_(char) {
return char >= "0" && char <= "9";
}
// Given a JavaScript 2d Array, this function returns the transposed table.
// Arguments:
// - data: JavaScript 2d Array
// Returns a JavaScript 2d Array
// Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]].
function arrayTranspose_(data) {
if (data.length == 0 || data[0].length == 0) {
return null;
}
var ret = [];
for (var i = 0; i < data[0].length; ++i) {
ret.push([]);
}
for (var i = 0; i < data.length; ++i) {
for (var j = 0; j < data[i].length; ++j) {
ret[j][i] = data[i][j];
}
}
return ret;
}
Related
Javascript includes and map together [duplicate]
I am supposed to write a program in JavaScript to find all the anagrams within a series of words provided. e.g.: monk, konm, nkom, bbc, cbb, dell, ledl, llde The output should be categorised into rows: 1. monk konm, nkom; 2. bbc cbb; 3. dell ledl, llde; I already sorted them into alphabetical order and put them into an array. i.e.: kmno kmno bbc bbc dell dell However I am stuck in comparing and finding the matching anagram within the array. Any help will be greatly appreciated.
Javascript objects are excellent for this purpose, since they are essentially key/value stores: // Words to match var words = ["dell", "ledl", "abc", "cba"]; // The output object var anagrams = {}; for (var i in words) { var word = words[i]; // sort the word like you've already described var sorted = sortWord(word); // If the key already exists, we just push // the new word on the the array if (anagrams[sorted] != null) { anagrams[sorted].push(word); } // Otherwise we create an array with the word // and insert it into the object else { anagrams[sorted] = [ word ]; } } // Output result for (var sorted in anagrams) { var words = anagrams[sorted]; var sep = ","; var out = ""; for (var n in words) { out += sep + words[n]; sep = ""; } document.writeln(sorted + ": " + out + "<br />"); }
Here is my take: var input = "monk, konm, bbc, cbb, dell, ledl"; var words = input.split(", "); for (var i = 0; i < words.length; i++) { var word = words[i]; var alphabetical = word.split("").sort().join(""); for (var j = 0; j < words.length; j++) { if (i === j) { continue; } var other = words[j]; if (alphabetical === other.split("").sort().join("")) { console.log(word + " - " + other + " (" + i + ", " + j + ")"); } } } where the output would be (the word, the match and the index of both): monk - konm (0, 1) konm - monk (1, 0) bbc - cbb (2, 3) cbb - bbc (3, 2) dell - ledl (4, 5) ledl - dell (5, 4) To get the characters in the in alphabetical order, I used split("") ot get an array, called sort() and used join("") to get a string from the array.
Simple Solution function anagrams(stringA, stringB) { return cleanString(stringA) === cleanString(stringB); } function cleanString(str) { return str.replace(/[^\w]/g).toLowerCase().split('').sort().join() } anagrams('monk','konm') If it is anagrams function will return true otherwise false
I worked through a similar question to this today and wanted to share the results of my work. I was focused on just detecting the anagram so processing the list of words was not part of my exercise but this algorithm should provide a highly performant way to detect an anagram between two words. function anagram(s1, s2){ if (s1.length !== s2.length) { // not the same length, can't be anagram return false; } if (s1 === s2) { // same string must be anagram return true; } var c = '', i = 0, limit = s1.length, match = 0, idx; while(i < s1.length){ // chomp the next character c = s1.substr(i++, 1); // find it in the second string idx = s2.indexOf(c); if (idx > -1) { // found it, add to the match match++; // assign the second string to remove the character we just matched s2 = s2.substr(0, idx) + s2.substr(idx + 1); } else { // not found, not the same return false; } } return match === s1.length; } I think technically is can be solved like this: function anagram(s1, s2){ return s1.split("").sort().join("") === s2.split("").sort().join(""); } The reason I chose the earlier approach is that it is more performant for larger strings since you don't need to sort either string, convert to an array or loop through the entire string if any possible failure case is detected.
Probably not the most efficient way, but a clear way around using es6 function sortStrChars(str) { if (!str) { return; } str = str.split(''); str = str.sort(); str = str.join(''); return str; } const words = ["dell", "ledl", "abc", "cba", 'boo']; function getGroupedAnagrams(words) { const anagrams = {}; // {abc:[abc,cba], dell:[dell, ledl]} words.forEach((word) => { const sortedWord = sortStrChars(word); if (anagrams[sortedWord]) { return anagrams[sortedWord].push(word); } anagrams[sortedWord] = [word]; }); return anagrams; } const groupedAnagrams = getGroupedAnagrams(words); for (const sortedWord in groupedAnagrams) { console.log(groupedAnagrams[sortedWord].toString()); }
I had this question in an interview. Given an array of words ['cat', 'dog', 'tac', 'god', 'act'], return an array with all the anagrams grouped together. Makes sure the anagrams are unique. var arr = ['cat', 'dog', 'tac', 'god', 'act']; var allAnagrams = function(arr) { var anagrams = {}; arr.forEach(function(str) { var recurse = function(ana, str) { if (str === '') anagrams[ana] = 1; for (var i = 0; i < str.length; i++) recurse(ana + str[i], str.slice(0, i) + str.slice(i + 1)); }; recurse('', str); }); return Object.keys(anagrams); } console.log(allAnagrams(arr)); //["cat", "cta", "act", "atc", "tca", "tac", "dog", "dgo", "odg", "ogd", "gdo", "god"]
Best and simple way to solve is using for loops and traversing it to each string and then store their result in object. Here is the solution :- function anagram(str1, str2) { if (str1.length !== str2.length) { return false; } const result = {}; for (let i=0;i<str1.length;i++) { let char = str1[i]; result[char] = result[char] ? result[char] += 1 : result[char] = 1; } for (let i=0;i<str2.length;i++) { let char = str2[i]; if (!result[char]) { return false; } else { result[char] = -1; } } return true; } console.log(anagram('ronak','konar'));
I know this is an ancient post...but I just recently got nailed during an interview on this one. So, here is my 'new & improved' answer: var AnagramStringMiningExample = function () { /* Author: Dennis Baughn * This has also been posted at: * http://stackoverflow.com/questions/909449/anagrams-finder-in-javascript/5642437#5642437 * Free, private members of the closure and anonymous, innner function * We will be building a hashtable for anagrams found, with the key * being the alphabetical char sort (see sortCharArray()) * that the anagrams all have in common. */ var dHash = {}; var sortCharArray = function(word) { return word.split("").sort().join(""); }; /* End free, private members for the closure and anonymous, innner function */ /* This goes through the dictionary entries. * finds the anagrams (if any) for each word, * and then populates them in the hashtable. * Everything strictly local gets de-allocated * so as not to pollute the closure with 'junk DNA'. */ (function() { /* 'dictionary' referring to English dictionary entries. For a real * English language dictionary, we could be looking at 20,000+ words, so * an array instead of a string would be needed. */ var dictionaryEntries = "buddy,pan,nap,toot,toto,anestri,asterin,eranist,nastier,ratines,resiant,restain,retains,retinas,retsina,sainter,stainer,starnie,stearin"; /* This could probably be refactored better. * It creates the actual hashtable entries. */ var populateDictionaryHash = function(keyword, newWord) { var anagrams = dHash[keyword]; if (anagrams && anagrams.indexOf(newWord) < 0) dHash[keyword] = (anagrams+','+newWord); else dHash[keyword] = newWord; }; var words = dictionaryEntries.split(","); /* Old School answer, brute force for (var i = words.length - 1; i >= 0; i--) { var firstWord = words[i]; var sortedFirst = sortCharArray(firstWord); for (var k = words.length - 1; k >= 0; k--) { var secondWord = words[k]; if (i === k) continue; var sortedSecond = sortCharArray(secondWord); if (sortedFirst === sortedSecond) populateDictionaryHash(sortedFirst, secondWord); } }/* /*Better Method for JS, using JS Array.reduce(callback) with scope binding on callback function */ words.reduce(function (prev, cur, index, array) { var sortedFirst = this.sortCharArray(prev); var sortedSecond = this.sortCharArray(cur); if (sortedFirst === sortedSecond) { var anagrams = this.dHash[sortedFirst]; if (anagrams && anagrams.indexOf(cur) < 0) this.dHash[sortedFirst] = (anagrams + ',' + cur); else this.dHash[sortedFirst] = prev + ','+ cur; } return cur; }.bind(this)); }()); /* return in a nice, tightly-scoped closure the actual function * to search for any anagrams for searchword provided in args and render results. */ return function(searchWord) { var keyToSearch = sortCharArray(searchWord); document.writeln('<p>'); if (dHash.hasOwnProperty(keyToSearch)) { var anagrams = dHash[keyToSearch]; document.writeln(searchWord + ' is part of a collection of '+anagrams.split(',').length+' anagrams: ' + anagrams+'.'); } else document.writeln(searchWord + ' does not have anagrams.'); document.writeln('<\/p>'); }; }; Here is how it executes: var checkForAnagrams = new AnagramStringMiningExample(); checkForAnagrams('toot'); checkForAnagrams('pan'); checkForAnagrams('retinas'); checkForAnagrams('buddy'); Here is the output of the above: toot is part of a collection of 2 anagrams: toto,toot. pan is part of a collection of 2 anagrams: nap,pan. retinas is part of a collection of 14 anagrams: stearin,anestri,asterin,eranist,nastier,ratines,resiant,restain,retains,retinas,retsina,sainter,stainer,starnie. buddy does not have anagrams.
My solution to this old post: // Words to match var words = ["dell", "ledl", "abc", "cba"], map = {}; //Normalize all the words var normalizedWords = words.map( function( word ){ return word.split('').sort().join(''); }); //Create a map: normalizedWord -> real word(s) normalizedWords.forEach( function ( normalizedWord, index){ map[normalizedWord] = map[normalizedWord] || []; map[normalizedWord].push( words[index] ); }); //All entries in the map with an array with size > 1 are anagrams Object.keys( map ).forEach( function( normalizedWord , index ){ var combinations = map[normalizedWord]; if( combinations.length > 1 ){ console.log( index + ". " + combinations.join(' ') ); } }); Basically I normalize every word by sorting its characters so stackoverflow would be acefkloorstvw, build a map between normalized words and the original words, determine which normalized word has more than 1 word attached to it -> That's an anagram.
Maybe this? function anagram (array) { var organized = {}; for (var i = 0; i < array.length; i++) { var word = array[i].split('').sort().join(''); if (!organized.hasOwnProperty(word)) { organized[word] = []; } organized[word].push(array[i]); } return organized; } anagram(['kmno', 'okmn', 'omkn', 'dell', 'ledl', 'ok', 'ko']) // Example It'd return something like { dell: ['dell', 'ledl'], kmno: ['kmno', okmn', 'omkn'], ko: ['ok', ko'] } It's a simple version of what you wanted and certainly it could be improved avoiding duplicates for example.
My two cents. This approach uses XOR on each character in both words. If the result is 0, then you have an anagram. This solution assumes case sensitivity. let first = ['Sower', 'dad', 'drown', 'elbow'] let second = ['Swore', 'add', 'down', 'below'] // XOR all characters in both words function isAnagram(first, second) { // Word lengths must be equal for anagram to exist if (first.length !== second.length) { return false } let a = first.charCodeAt(0) ^ second.charCodeAt(0) for (let i = 1; i < first.length; i++) { a ^= first.charCodeAt(i) ^ second.charCodeAt(i) } // If a is 0 then both words have exact matching characters return a ? false : true } // Check each pair of words for anagram match for (let i = 0; i < first.length; i++) { if (isAnagram(first[i], second[i])) { console.log(`'${first[i]}' and '${second[i]}' are anagrams`) } else { console.log(`'${first[i]}' and '${second[i]}' are NOT anagrams`) } }
function isAnagram(str1, str2) { var str1 = str1.toLowerCase(); var str2 = str2.toLowerCase(); if (str1 === str2) return true; var dict = {}; for(var i = 0; i < str1.length; i++) { if (dict[str1[i]]) dict[str1[i]] = dict[str1[i]] + 1; else dict[str1[i]] = 1; } for(var j = 0; j < str2.length; j++) { if (dict[str2[j]]) dict[str2[j]] = dict[str2[j]] - 1; else dict[str2[j]] = 1; } for (var key in dict) { if (dict[key] !== 0) return false; } return true; } console.log(isAnagram("hello", "olleh"));
I have an easy example function isAnagram(strFirst, strSecond) { if(strFirst.length != strSecond.length) return false; var tempString1 = strFirst.toLowerCase(); var tempString2 = strSecond.toLowerCase(); var matched = true ; var cnt = 0; while(tempString1.length){ if(tempString2.length < 1) break; if(tempString2.indexOf(tempString1[cnt]) > -1 ) tempString2 = tempString2.replace(tempString1[cnt],''); else return false; cnt++; } return matched ; } Calling function will be isAnagram("Army",Mary); Function will return true or false
let words = ["dell", "ledl","del", "abc", "cba", 'boo']; //sort each item function sortArray(data){ var r=data.split('').sort().join().replace(/,/g,''); return r; } var groupObject={}; words.forEach((item)=>{ let sorteditem=sortArray(item); //Check current item is in the groupObject or not. //If not then add it as an array //else push it to the object property if(groupObject[sorteditem]) return groupObject[sorteditem].push(item); groupObject[sorteditem]=[sorteditem]; }); //to print the result for(i=0;i<Object.keys(groupObject).length;i++) document.write(groupObject[Object.keys(groupObject)[i]] + "<br>"); /* groupObject value: abc: (2) ["abc", "cba"] boo: ["boo"] del: ["del"] dell: (2) ["dell", "ledl"] OUTPUT: ------ dell,ledl del abc,cba boo */
Compare string length, if not equal, return false Create character Hashmap which stores count of character in strA e.g. Hello --> {H: 1, e: 1, l: 2, o: 1} Loop over the second string and lookup the current character in Hashmap. If not exist, return false, else decrement the value by 1 If none of the above return falsy, it must be an anagram Time complexity: O(n) function isAnagram(strA: string, strB: string): boolean { const strALength = strA.length; const strBLength = strB.length; const charMap = new Map<string, number>(); if (strALength !== strBLength) { return false; } for (let i = 0; i < strALength; i += 1) { const current = strA[i]; charMap.set(current, (charMap.get(current) || 0) + 1); } for (let i = 0; i < strBLength; i += 1) { const current = strB[i]; if (!charMap.get(current)) { return false; } charMap.set(current, charMap.get(current) - 1); } return true; }
function findAnagram(str1, str2) { let mappedstr1 = {}, mappedstr2 = {}; for (let item of str1) { mappedstr1[item] = (mappedstr1[item] || 0) + 1; } for (let item2 of str2) { mappedstr2[item2] = (mappedstr2[item2] || 0) + 1; } for (let key in mappedstr1) { if (!mappedstr2[key]) { return false; } if (mappedstr1[key] !== mappedstr2[key]) { return false; } } return true; } console.log(findAnagram("hello", "hlleo"));
Another example only for comparing 2 strings for an anagram. function anagram(str1, str2) { if (str1.length !== str2.length) { return false; } else { if ( str1.toLowerCase().split("").sort().join("") === str2.toLowerCase().split("").sort().join("") ) { return "Anagram"; } else { return "Not Anagram"; } } } console.log(anagram("hello", "olleh")); console.log(anagram("ronak", "konar"));
const str1 ="1123451" const str2 = "2341151" function anagram(str1,str2) { let count = 0; if (str1.length!==str2.length) { return false;} for(i1=0;i1<str1.length; i1++) { for (i2=0;i2<str2.length; i2++) { if (str1[i1]===str2[i2]){ count++; break; } } } if (count===str1.length) { return true} } anagram(str1,str2)
Another solution for isAnagram using reduce const checkAnagram = (orig, test) => { return orig.length === test.length && orig.split('').reduce( (acc, item) => { let index = acc.indexOf(item); if (index >= 0) { acc.splice(index, 1); return acc; } throw new Error('Not an anagram'); }, test.split('') ).length === 0; }; const isAnagram = (tester, orig, test) => { try { return tester(orig, test); } catch (e) { return false; } } console.log(isAnagram(checkAnagram, '867443', '473846')); console.log(isAnagram(checkAnagram, '867443', '473846')); console.log(isAnagram(checkAnagram, '867443', '475846'));
var check=true; var str="cleartrip"; var str1="tripclear"; if(str.length!=str1.length){ console.log("Not an anagram"); check=false; } console.log(str.split("").sort()); console.log("----------"+str.split("").sort().join('')); if(check){ if((str.split("").sort().join(''))===((str1.split("").sort().join('')))){ console.log("Anagram") } else{ console.log("not a anagram"); } }
Here is my solution which addresses a test case where the input strings which are not anagrams, can be removed from the output. Hence the output contains only the anagram strings. Hope this is helpful. /** * Anagram Finder * #params {array} wordArray * #return {object} */ function filterAnagram(wordArray) { let outHash = {}; for ([index, word] of wordArray.entries()) { let w = word.split("").sort().join(""); outHash[w] = !outHash[w] ? [word] : outHash[w].concat(word); } let filteredObject = Object.keys(outHash).reduce(function(r, e) { if (Object.values(outHash).filter(v => v.length > 1).includes(outHash[e])) r[e] = outHash[e] return r; }, {}); return filteredObject; } console.log(filterAnagram(['monk', 'yzx','konm', 'aaa', 'ledl', 'bbc', 'cbb', 'dell', 'onkm']));
i have recently faced this in the coding interview, here is my solution. function group_anagrams(arr) { let sortedArr = arr.map(item => item.split('').sort().join('')); let setArr = new Set(sortedArr); let reducedObj = {}; for (let setItem of setArr) { let indexArr = sortedArr.reduce((acc, cur, index) => { if (setItem === cur) { acc.push(index); } return acc; }, []); reducedObj[setItem] = indexArr; } let finalArr = []; for (let reduceItem in reducedObj) { finalArr.push(reducedObj[reduceItem].map(item => arr[item])); } return finalArr; } group_anagrams(['car','cra','rca', 'cheese','ab','ba']); output will be like [ ["car", "cra", "rca"], ["cheese"], ["ab", "ba"] ]
My solution has more code, but it avoids using .sort(), so I think this solution has less time complexity. Instead it makes a hash out of every word and compares the hashes: const wordToHash = word => { const hash = {}; // Make all lower case and remove spaces [...word.toLowerCase().replace(/ /g, '')].forEach(letter => hash[letter] ? hash[letter] += 1 : hash[letter] = 1); return hash; } const hashesEqual = (obj1, obj2) => { const keys1 = Object.keys(obj1), keys2 = Object.keys(obj2); let match = true; if(keys1.length !== keys2.length) return false; for(const key in keys1) { if(obj1[key] !== obj2[key]) match = false; break; } return match; } const checkAnagrams = (word1, word2) => { const hash1 = wordToHash(word1), hash2 = wordToHash(word2); return hashesEqual(hash1, hash2); } console.log( checkAnagrams("Dormitory", "Dirty room") );
/*This is good option since logic is easy, deals with duplicate data, Code to check anagram in an array, shows results in appropriate manner, function check can be separately used for comparing string in this regards with all benefits mentioned above. */ var words = ["deuoll", "ellduo", "abc","dcr","frt", "bu","cba","aadl","bca","elduo","bac","acb","ub","eldou","ellduo","ert","tre"]; var counter=1; var ele=[]; function check(str1,str2) { if(str2=="") return false; if(str1.length!=str2.length) return false; var r1=[...(new Set (str1.split('').sort()))]; var r2=[...(new Set (str2.split('').sort()))]; var flag=true; r1.forEach((item,index)=> { if(r2.indexOf(item)!=index) { flag=false;} }); return flag; } var anagram=function () { for(var i=0;i<words.length && counter!=words.length ;i++) { if(words[i]!="") { document.write("<br>"+words[i]+":"); counter++; } for(var j=i+1;j<words.length && counter !=words.length+1;j++) { if(check(words[i],words[j])) { ele=words[j]; document.write(words[j]+" "); words[j]=""; counter++; } } } } anagram();
If you just need count of anagrams const removeDuplicatesAndSort = [...new Set(yourString.split(', '))].map(word => word.split('').sort().join()) const numberOfAnagrams = removeDuplicatesAndSort.length - [...new Set(removeDuplicatesAndSort)].length
function isAnagram(str1, str2){ let count = 0; if (str1.length !== str2.length) { return false; } else { let val1 = str1.toLowerCase().split("").sort(); let val2 = str2.toLowerCase().split("").sort(); for (let i = 0; i < val2.length; i++) { if (val1[i] === val2[i]) { count++; } } if (count == str1.length) { return true; } } return false; } console.log(isAnagram("cristian", "Cristina"))
function findAnagrams (str, arr){ let newStr = ""; let output = []; for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr[i].length; j++) { for (let k = 0; k < str.length; k++) { if (str[k] === arr[i][j] && str.length === arr[i].length) { newStr += arr[i][j]; } } } if(newStr.length === str.length){ output.push(newStr); newStr = ""; } } return output; }
const getAnagrams = (...args) => { const anagrams = {}; args.forEach((arg) => { const letters = arg.split("").sort().join(""); if (anagrams[letters]) { anagrams[letters].push(arg); } else { anagrams[letters] = [arg]; } }); return Object.values(anagrams); }
function isAnagaram(str1, str2){ if(str1.length!== str2.length){ return false; } var obj1 = {}; var obj2 = {}; for(var arg of str1){ obj1[arg] = (obj1[arg] || 0 ) + 1 ; } for(var arg of str2){ obj2[arg] = (obj2[arg] || 0 ) + 1 ; } for( var key in obj1){ if(obj1[key] !== obj2[key]){ return false; } } return true; } console.log(isAnagaram('texttwisttime' , 'timetwisttext'));
let validAnagram = (firstString, secondString) => { if (firstString.length !== secondString.length) { return false; } let secondStringArr = secondString.split(''); for (var char of firstString) { charIndexInSecondString = secondString.indexOf(char); if (charIndexInSecondString === -1) { return false; } secondString = secondString.replace(char, ''); } return true; }
How to returns correctly matched array?
I have a function that takes a string of DNA and how to return correctly matched dna array The code that I have tried: function checkDNA(dna) { var dnaarr = []; for(var i = 0; i < dna.length; i++) { var str = []; str.push(dna[i]); //pushing current str[i] if(dna[i].indexOf('') === 0) { var a = str.push('sd'); } if(dna[i].indexOf('GGC') === 0) { var b = str.push("GC", "GC", "CG"); } if(dna[i].indexOf('gat') === 0) { var c = str.push("GC", "AT", "TA"); } if(dna[i].indexOf('PGYYYHVB') === 0) { var d = str.push('GC'); } dnaarr.push(str); //pushing the array dna to the main array dnaarr } return dnaarr; }
You could take an object for the nucleobases and take the characters of the string for getting the values for each character. For unknow characters, a later filtering is applied. function pair(string) { var nucleobases = { G: 'C', C: 'G', T: 'A', A: 'T' }; return Array .from(string.toUpperCase(), s => s in nucleobases && s + nucleobases[s]) .filter(Boolean); } console.log(pair('GTTC'));
You are thinking this in the wrong way. You have to register those values. Think about in the db way. Here is an exmaple. var dnaRules = {} /// register DNA register = function(key) { var item = {}; if (dnaRules[key]) return dnaRules[key]; else { item[key]= []; item.equal = function(value){ item["data"] = value; } dnaRules[key]= item; } return item; } register("GGC").equal(['GC', 'GC', 'CG']); register("gat").equal(["GC", "AT", "TA"]); register("PGYYYHVB").equal(["GC", "GC", "GC"]); // Search DNA search = function(value){ var key = null; Object.keys(dnaRules).forEach(function(v){ if (v.toLowerCase().indexOf(value.toLowerCase())>=0) key = v; }); return key== null ? null : dnaRules[key] } var item = search("ggc"); // if the item dose not exist you get null // now display the items console.log(item.data) /// or you could change them even // item.equal([])
seprate values from a string
I have 2 string like this var a= '12,13,14,15,16'; var b='p,q,q,p,q'; I just need like this 12,15 represents p and 13,14,16 represents q How can I do this in Jquery/javascript.
var a = '12,13,14,15,16'; var b = 'p,q,q,p,q'; var as = a.split(","); var bs = b.split(","); if(as.length == bs.length) { var map = {}; for(var i = 0; i < as.length; ++i) { var asv = as[i]; var bsv = bs[i]; map[asv] = bsv; } console.log(map['13']); //q } or: var a = '12,13,14,15,16'; var b = 'p,q,q,p,q'; var as = a.split(","); var bs = b.split(","); if(as.length == bs.length) { var map = {}; as.map(function(asv,idx){ return {asv:asv, bsv:bs[idx]}; }) .forEach(function(x){ map[x.asv] = x.bsv; }); console.log(map['13']); //q } In answer to your comment, perhaps something like this would be better: var a = '12,13,14,15,16'; var b = 'p,q,q,p,q'; var as = a.split(","); var bs = b.split(","); if(as.length == bs.length) { var map = {}; as.map(function(asv,idx){ return {asv:asv,bsv:bs[idx]}; }) .forEach(function(x){ if(!map[x.bsv]) { map[x.bsv]=[]; } map[x.bsv].push(x.asv); }); console.log(map['q']); //["13", "14", "16"] console.log(map['q'].join(",")); //"13,14,16" }
Simple as this: var a= '12,13,14,15,16'; var b='p,q,q,p,q'; var pValues=[], qValues=[]; //where to store the results b.split(',').forEach(function(value, index){ //split the values from the b var var aa=a.split(','); //split the values from the a var if(value=='p') pValues.push(aa[index]); if(value=='q') qValues.push(aa[index]); }); console.log("pValues: "+pValues); console.log("qValues: "+qValues);
var a= '12,13,14,15,16'; var b='p,q,q,p,q'; function getRepresentative(srcA, srcB, valA) { var mapIndexA = srcA && srcA.split(',').indexOf(valA); var mapB = srcB.split(','); return mapB && mapB[mapIndexA] || -1; } console.log(getRepresentative(a,b, '15')); The function returns -1 if no corresponding map between A and B is found..
The following function takes the two strings and splits them with the comma, then iterates over the symbol-token pairings one by one. Whenever a new symbol is discovered, it gets added to symbols and an empty array is added to symbolToNumbers so that numbers for this symbol can be pushed onto the array. At the end, we can iterate over symbols to display the list of numbers for each symbol. This code will work for any variety of symbols, not just 'p' and 'q'. function findElements(numbers, symbols) { var numberTokens = numbers.split(','), symbolTokens = symbols.split(','), symbolToNumbers = {}, symbols = [], n = numberTokens.length; if (n != symbolTokens.length) { console.log('error: the lists must have the same length'); return; } for (var i = 0; i < n; ++i) { var number = numberTokens[i], symbol = symbolTokens[i]; if (symbolToNumbers[symbol] === undefined) { symbols.push(symbol); symbolToNumbers[symbol] = []; } symbolToNumbers[symbol].push(number); } for (var i = 0; i < symbols.length; ++i) { var symbol = symbols[i], numbers = symbolToNumbers[symbol]; console.log(symbol+': '+numbers.join(', ')); } } var a = '12,13,14,15,16'; var b = 'p,q,q,p,q'; findElements(a, b); See this code running on JSFiddle: http://jsfiddle.net/0e1g2ryf/1/
Create an array [n,[v,..,z]] from a list of key-value pairs
I have this input sample: var c1 = "s_A_3"; var c2 = "s_B_10"; var c3 = "s_B_9"; var c4 = "s_C_18"; var c5 = "s_C_19"; var c6 = "s_C_20"; Which can easily be concatenated to: var keypairs = ["A_3","B_10","B_9","C_18","C_19","C_20"]; And I want to convert this to a multidimensional array like this: var groupArray = [["A",[3]],["B",[10,9]],["C",[18,19,20]]]; It's like a kind of card-sorting. How can I achieve this?
Maybe something like this: function makeGroups(arr) { var result = [], prev; for(var i = 0; i < arr.length; i++) { var x = arr[i].split("_"); if (prev !== x[0]) { prev = x[0]; result.push([prev, []]); } result[result.length - 1][1].push(x[1]); // or .push(parseInt(x[1], 10)) } return result; } var keypairs = ["A_3","B_10","B_9","C_18","C_19","C_20"]; console.log(makeGroups(keypairs)); // [["A",["3"]],["B",["10","9"]],["C",["18","19","20"]]] Demonstration The above method assumes the groups will be contiguous (e.g. all B_ elements appear together). In case your input may be out of order, you can tweak this algorithm to still group all elements together regardless of where they appear in the input: function makeGroups(arr) { var result = [], keys = {}; for(var i = 0; i < arr.length; i++) { var x = arr[i].split("_"); if (!(x[0] in keys)) { keys[x[0]] = []; result.push([x[0], keys[x[0]]]); } keys[x[0]].push(x[1]); // or .push(parseInt(x[1], 10)) } return result; } var keypairs = ["A_3","B_10","C_18","C_19","C_20","B_9"]; console.log(makeGroups(keypairs)); // [["A",["3"]],["B",["10","9"]],["C",["18","19","20"]]] Demonstration
When you need to mention "key value pairs" in a JS program, it's usually most appropriate to use... key value pairs =D. function solution(input) { var kvp = {}, result = []; input.forEach(function (el) { var cut = el.split("_"), alpha = cut[0], numeric = cut[1], elsWithSameAlpha = kvp[alpha] = kvp[alpha] || []; elsWithSameAlpha.push(numeric); }); Object.keys(kvp).forEach(function (key) { result.push([key, kvp[key]]); }); return result; }
Javascript group linked key values into arrays
Hi I have a very long list of key value pairs in json key:value, key:value and so on car <--> wheel wheel <--> tyre bed <--> sheets guitar <--> strings guitar <--> pickup tyre <--> rubber What I want is to group all relations into arrays no matter how distant like this [car, wheel, tyre, rubber] [guitar, strings, pickup] [bed, sheets] What is an efficient way to do this with Javascript?
First of all, I would store the relationships as arrays so that you can have duplicate "keys." Key methods: an initial dictionary including every word related to each individual word; a recursive chain expander using map and reduce; filtering chains based on equivalency. Array.prototype.getUnique = function(){ var u = {}, a = []; for(var i = 0, l = this.length; i < l; ++i){ if(u.hasOwnProperty(this[i])) { continue; } a.push(this[i]); u[this[i]] = 1; } return a; } var links = {}; var pairs = [ ["car", "wheel"], ["wheel", "tyre"], ["bed", "sheets"], ["guitar", "strings"], ["guitar", "pickup"], ["rubber", "tyre"], ["truck", "wheel"], ["pickup", "car"] ]; pairs.map(function(pair) { links[pair[0]] = links[pair[0]] || []; links[pair[1]] = links[pair[1]] || []; links[pair[0]].push(pair[1]); links[pair[1]].push(pair[0]); }); var append = function(list) { var related = list.map(function(item) { return links[item]; }).reduce(function(listA, listB) { return listA.concat(listB); }).filter(function(item) { // make sure related only includes new links return list.indexOf(item) == -1 }).getUnique(); return related.length ? append(list.concat(related)) : list.concat(related); }; var branches = []; for( var word in links ) { branches.push(append(links[word].concat(word))); } var compareArrays = function(listA, listB) { if( listA.length != listB.length ) return false; return listA.map(function(element) { if( listB.indexOf(element) == -1 ) return 0; return 1; }).filter(function(el) { return el == 1; }).length == listA.length; }; var _branches = branches; var chains = branches.filter(function(branch1, i) { var isUnique = _branches.filter(function(branch2) { // are they equivalent return compareArrays(branch1, branch2); }).length == 1; delete _branches[i]; return isUnique; });
I'd go with a map of words, linking the sets they are currently in. The map (a javascript object) with nearly O(1) runtime for accessing a key should help the performance. Start with the same format as proposed by #matt3141: var pairs = [ ["car", "wheel"], ["wheel", "tyre"], ["bed", "sheets"], ["guitar", "strings"], ["guitar", "pickup"], ["rubber", "tyre"], ["truck", "wheel"], ["pickup", "car"] ]; var setsByWord = {}; for (var i=0; i<pairs.length; i++) { var pair = pairs[i]; if (pair[0] in setsByWord && pair[1] in setsByWord) { // both words are already known if (setsByWord[pair[0]] === setsByWord[pair[1]]) { ; // We're lucky, they are in the same set } else { // combine the two sets var sets = [setsByWord[pair[0]], setsByWord[pair[1]]]; var larger = sets[1].length > sets[0].length ? sets[1] : sets[0], smaller = sets[+(larger===sets[0])]; for (var j=0; j<smaller.length; j++) setsByWord[smaller[j]] = larger; Array.prototype.push.apply(larger, smaller); } } else { // add the missing word to the existing set // or create a new set var set = setsByWord[pair[0]] || setsByWord[pair[1]] || []; if (!(pair[0] in setsByWord)) { set.push(pair[0]); setsByWord[pair[0]] = set; } if (!(pair[1] in setsByWord)) { set.push(pair[1]); setsByWord[pair[1]] = set; } } } return setsByWord; This will split your graph in its connected components (In the setsByWord object these component arrays are indexed by the nodes): > var results = []; > for (var word in setsByWord) > if (results.indexOf(setsByWord[word])<0) > results.push(setsByWord[word]); > return results; [ ["car","wheel","tyre","rubber","truck","guitar","strings","pickup"], ["bed","sheets"] ] If you have a directed graph, and want arrays of all successors by word, you could use this: var pairs = […], graph = pairs.reduce(function(map, pair) { (map[pair[0]] || (map[pair[0]] = [])).push(pair[1]); return map; }, {}); var successors = {}; for (var word in graph) (function getSuccessors(word) { if (word in successors) return successors[word]; successors[word] = [true]; // some marker against circles return successors[word] = word in graph ? [].concat.apply(graph[word], graph[word].map(getSuccessors)) : []; })(word); return successors; If you are sure to have no circles in the graph and only want lists for the beginners of paths, you might add this: var results = []; for (var word in successors) for (var i=0; word in successors && i<successors[word].length; i++) delete successors[successors[word][i]]; for (var word in successors) results.push([word].concat(successors[word])); return results; // becomes: [ ["bed","sheets"], ["guitar","strings","pickup","car","wheel","tyre"], ["rubber","tyre"], ["truck","wheel","tyre"] ]