I am doing the below to get certain nodes from a treeview followed by getting text from those nodes, filtering text to remove unique and then appending custom image to the duplicate nodes.
For this I am having to loop 4 times. Is there is a simpler way of doing this? I am worried about it's performance for large amount of data.
//Append duplicate item nodes with custom icon
function addRemoveForDuplicateItems() {
var treeView = $('#MyTree').data('t-TreeView li.t-item');
var myNodes = $("span.my-node", treeView);
var myNames = [];
$(myNodes).each(function () {
myNames.push($(this).text());
});
var duplicateItems = getDuplicateItems(myNames);
$(myNodes).each(function () {
if (duplicateItems.indexOf($(this).text()) > -1) {
$(this).parent().append(("<span class='remove'></span>"));
}
});
}
//Get all duplicate items removing unique ones
//Input [1,2,3,3,2,2,4,5,6,7,7,7,7] output [2,3,3,2,2,7,7,7,7]
function getDuplicateItems(myNames) {
var duplicateItems = [], itemOccurance = {};
for (var i = 0; i < myNames.length; i++) {
var dept = myNames[i];
itemOccurance[dept] = itemOccurance[dept] >= 1 ? itemOccurance[dept] + 1 : 1;
}
for (var item in itemOccurance) {
if (itemOccurance[item] > 1)
duplicateItems.push(item);
}
return duplicateItems;
}
If I understand correctly, the whole point here is simply to mark duplicates, right? You ought to be able to do this in two simpler passes:
var seen = {};
var SEEN_ONCE = 1;
var SEEN_DUPE = 2;
// First pass, build object
myNodes.each(function () {
var name = $(this).text();
var seen = seen[name];
seen[name] = seen ? SEEN_DUPE : SEEN_ONCE;
});
// Second pass, append node
myNodes.each(function () {
var name = $(this).text();
if (seen[name] === SEEN_DUPE) {
$(this).parent().append("<span class='remove'></span>");
}
});
If you're actually concerned about performance, note that iterating over DOM elements is much more of a performance concern than iterating over an in-memory array. The $(myNodes).each(...) calls are likely significantly more expensive than iteration over a comparable array of the same length. You can gain some efficiencies from this, by running the second pass over an array and only accessing DOM nodes as necessary:
var names = [];
var seen = {};
var SEEN_ONCE = 1;
var SEEN_DUPE = 2;
// First pass, build object
myNodes.each(function () {
var name = $(this).text();
var seen = seen[name];
names.push(name);
seen[name] = seen ? SEEN_DUPE : SEEN_ONCE;
});
// Second pass, append node only for dupes
names.forEach(function(name, index) {
if (seen[name] === SEEN_DUPE) {
myNodes.eq(index).parent()
.append("<span class='remove'></span>");
}
});
The approach of this code is to go through the list, using the property name to indicate whether the value is in the array. After execution, itemOccurance will have a list of all the names, no duplicates.
var i, dept, itemOccurance = {};
for (i = 0; i < myNames.length; i++) {
dept = myNames[i];
if (typeof itemOccurance[dept] == undefined) {
itemOccurance[dept] = true;
}
}
If you must keep getDuplicateItems() as a separate, generic function, then the first loop (from myNodes to myNames) and last loop (iterate myNodes again to add the span) would be unavoidable. But I am curious. According to your code, duplicateItems can just be a set! This would help simplify the 2 loops inside getDuplicateItems(). #user2182349's answer just needs one modification: add a return, e.g. return Object.keys(itemOccurance).
If you're only concerned with ascertaining duplication and not particularly concerned about the exact number of occurrences then you could consider refactoring your getDuplicateItems() function like so:
function getDuplicateItems(myNames) {
var duplicateItems = [], clonedArray = myNames.concat(), i, dept;
for(i=0;i<clonedArray.length;i+=1){
dept = clonedArray[i];
if(clonedArray.indexOf(dept) !== clonedArray.lastIndexOf(dept)){
if(duplicateItems.indexOf(dept) === -1){
duplicateItems.push(dept);
}
/* Remove duplicate found by lastIndexOf, since we've already established that it's a duplicate */
clonedArray.splice(clonedArray.lastIndexOf(dept), 1);
}
}
return duplicateItems;
}
Related
I am creating a search bar, that filters an array with French city names that match the input value with each keyup. I am getting some results back (using the console log), but the results are not what I expect and the IF condition I am using doesn't seem to be functioning...
Based in JavaScript and a little jQuery to get my list of cities from a text file, I've tried using indexOf and match function, but using slice() seems to have got me the furthest. I'm missing something somewhere to push the matched results into a new variable/array.
var cityArray = [];
$.get("liste.txt", function (data) {
cityArray = data.split("\n").sort();
console.log(cityArray);
});
$("#myInput").keyup(function () {
var searchedWord = $(this).val();
var counter = searchedWord.length;
var result = [];
console.log(searchedWord);
// console.log(typeof searchedWord);
// console.log(counter);
var matchedCities = 0;
if (counter < 3) {
return;
}
for (var i = 0; i < 100; i++) {
var city = cityArray.slice(0, cityArray.length);
console.log(city[i].slice(0, searchedWord.length));
if (city[i].slice(0, searchedWord.length) == searchedWord) {
result.push(city[i]);
console.log(result);
matchedCities++;
console.log(matchedCities);
if (matchedCities > 5) {
break;
}
}
}
});
So I am expecting to see matched cities with the input, in the console log. BUT, the function seems to break down before then as the result and matchedCities variables don't seem to change even when I know I have typed a city that is in the list.
You can use Array.filter and Array.includes to do your search. Something like this:
$("#myInput").keyup(function () {
var searchedWord = $(this).val();
var result = cityArray.filter(c => c.toLowerCase().includes(searchedWord.toLowerCase()))
console.log(result)
});
You can add the rest of the code you need but the idea is to "search" in this fashion.
this is my code:
start() {
let columns = ['A'...'Z'];
let fields = {
id: [
'Medlemsnummer',
],
name: [
'Namn',
],
};
let out = {};
let self = this;
columns.forEach(function(column) {
for(let row = 1; row < 101; row++) {
let cell = column + row;
let d_cell = self.worksheet[cell];
let val_cell = (d_cell ? d_cell.v : ' ');
let cell_string = val_cell.toString().toLowerCase();
let cellString_stripped = cell_string.replace(/(?:\r\n|\r|\n)/g, '');
for (var key in fields) {
// skip loop if the property is from prototype
if (!fields.hasOwnProperty(key)) continue;
var obj = fields[key];
for (var prop in obj) {
// skip loop if the property is from prototype
if(!obj.hasOwnProperty(prop)) continue;
obj.forEach(function(term) {
if(cellString_stripped.match(new RegExp(term.toLowerCase() + ".*"))){
//out.push(obj + ': ' + cell);
//out[obj] = {cell};
out[obj] = cell;
}
});
//out[obj]
}
}
}
});
console.log(out);
},
and my problem is that i want several matched cells in out[obj] = // array of matched cells.
how can i do this in javascript?
so my out should look like this:
out = [ medlemsnummer: ['A11','A23','A45'], name: ['B11','B23'] etc... ]
please comment if you need me to explain better.
Kind regards,
Joakim
Looking at your loops, I think you got a little lost in your own structures. out[obj] = cell definitely doesn't seem right; obj is an object, it cannot be used as a key in another object. Here's my take with some notes, hope I interpreted both your code and your question correctly. I'm starting from the loop after all your variables like cell, d_cell, etc. are initialized):
for (let key in fields) {
if (!fields.hasOwnProperty(key)) continue;
let terms = fields[key];
// fields[key] yields us an array, e.g.:
// fields['id'] = [ 'Medlemnummer' ]
// so we can iterate over it directly with for..of.
// Note also: variable names like "obj" make reading your code
// difficult; use meaningful names, e.g. "terms".
for (let term of terms) {
let regex = new RegExp(term.toLowerCase() + ".*");
// Note: RegEx.test() is more efficient than String.match()
// if all you need is a yes/no answer.
if (!regex.test(cellString_stripped)) continue;
// Here's the part you actually needed help with:
if (!out[term]) {
out[term] = [];
}
out[term].push(cell);
}
}
Addendum: In the code I'm sticking with your solution to use RegExp to test the strings. However, if all you need to check is whether the string starts with the given substring, then it's much shorter and more efficient to use String.startsWith():
for (let term of terms) {
if (!cellString_stripped.startsWith(term.toLowerCase())) continue;
// Here's the part you actually needed help with:
if (!out[term]) {
out[term] = [];
}
out[term].push(cell);
}
I have two really long arrays containing "picture names" and "picture files". The first one represents the actual name of the pictures, while the second one is just the file name. For example:
picturenames[0] = '0 - zero';
picturenames[1] = '1 - one';
picturenames[2] = '1 o\'clock';
...
picturefiles[0] = 'numbers-zero.jpg';
picturefiles[1] = 'numbers-one.jpg';
picturefiles[2] = 'time-1.jpg';
...
I have about 1000 items in each array in several languages (the picture files are always the same). I'm "recycling" these arrays from the previous application to save some time and avoid rewriting everything anew.
Desirable functionality: using the user's input in a textbox I want to filter the picturenames array and then show the correspondant picturefiles image.
The issue I'm facing: when I filter the picturenames array I lose the index and I can't "reach" the picture file name.
This is the code I'm using to filter the picturenames array.
var matches = picturenames.filter(function(windowValue){
if(windowValue) {
return windowValue.indexOf(textToFindLower) >= 0;
}
});
What would be the best way to do this?
UPDATE: the solution proposed by Ahmed is the best one, but for time reasons and negligible performance issues I'm just using a for loop to search trough the array, as follows:
var matchesCounter = new Array();
for (i = 0; i < picturenames.length; i++) {
if (picturenames[i].indexOf(textToFindLower) >= 0) {
matchesCounter.push(i);
}
}
console.log(matchesCounter);
for (i = 0; i < matchesCounter.length; i++) {
console.log(picturenames[i]);
console.log(picturefiles[i]);
}
Try this:
const foundIndicies = Object.keys(picturenames).filter(pictureName => {
pictureName.includes(textToFindLower)
});
// reference picturefiles[foundIndicies[0]] to get the file name
Though, it would be far nicer to have both the name and the file in a single object, like so:
const pictures = [
{
name: '0 - zero',
file: 'numbers-zero.jpg',
},
{
name: '1 - one',
file: 'numbers-one.jpg',
}
];
const foundPictures = pictures.filter(picture => picture.name.includes('zero'));
if (foundPictures[0]) console.log(foundPictures[0].file);
You can add one property index during the filtering time, then later on you can use the index.
var matches = picturenames.filter(function(windowValue, index){
if(windowValue) {
windowValue.index = index;
return windowValue.comparator(textToFindLower) >= 0;// Need to define comparator function
}
});
Later on you can access by using like follows:
picturefiles[matches[0].index]
However, the solution will work on object, not primitive type string.
If your data type is string, then you have to convert as object and put the string as a property value like name. The snippet is given below:
var picturenames = [];
var picturefiles = [];
picturenames.push({name:'0 - zero'});
picturenames.push({name:'1 - one'});
picturenames.push({name:'1 o\'clock'});
picturefiles.push({name:'numbers-zero.jpg'});
picturefiles.push({name:'numbers-one.jpg'});
picturefiles.push({name: 'time-1.jpg'});
var textToFindLower = "0";
var matches = picturenames.filter(function(windowValue, index){
if(windowValue) {
windowValue.index = index;
return windowValue.name.indexOf(textToFindLower) >= 0;
}
});
console.log(matches);
After being stuck for a few hours on this problem, i think it is time for call for help on this.
Situation
I have a XML file which i need to filter and group. I've managed to filter it with the :Contains part. I've also determined the nodes on which i need to group (the getGroups function gives those back to me). Now i want to create a new XML with the filtered values and grouped by the returned keys.
Code
var XMLElement = document.createElement("DataElementsCalc");
jQuery(xml).find("DataElements " + topNodes + filter).each( function() {
var dataSetTemp = this.parentNode;
if(calculation1 != "")
{
var groupKeys = getGroups(dataSetTemp,calculation1);
var tempXML = XMLElement;
jQuery(groupKeys).each(function (key,value) {
var tempValue = 'a' + value.toLowerCase().replace(/\W/g, '');
if(tempXML.getElementsByTagName(tempValue).length > 0)
{
tempXML = tempXML.getElementsByTagName(tempValue);
}
else
{
var Node = document.createElement(tempValue);
tempXML.appendChild(Node);
tempXML = Node;
}
});
var Node = document.createElement("InfoSet");
var x = dataSetTemp.childNodes;
for (i=0; i < x.length; i++)
{
if(x[i].nodeType == 1)
{
var tempElement = document.createElement(x[i].nodeName);
tempElement.innerHTML = x[i].childNodes[0].nodeValue;
Node.appendChild(tempElement);
}
}
tempXML.appendChild(Node);
}
});
Explanation
As said in the situation, i already filtered the XML and have the groupNames from the getGroups function. There are a few other things i need to explain for this code:
tempValue is being build as a a + value.toLowerCase().replace(/\W/g, '');. This is being done because i possible get dates into the groupKeys. This way the node name is getting a working name (i received errors on other ways).
I want to create a new XML which is leveled by the groups. If a group already exists, i want to create a new element in it, not get a new group with the same name. (my problem at the moment).
Problem
As mentioned above, the groups aren't checked properly. Firstly: tempXML.getElementsByTagName(tempValue).length returns the error that the function tempXML.getElementsByTagName does not exists. Secondly: If i change this to document.getElemetsByTagName I get a lot of the same nodes in my XML file.
Effect
The grouping effect doesn't work as it should. I get OR an error, OR a lot of the same nodes in my DataElementsCalc.
Questions
How can i solve this? How do create nodes beneath specific nodes (for if there is a group A beneath group 1 as well as beneath group 2)?
Tried
Change tempXML to document on different places (all getElementsByTagName, at the same time or not). Looked for another way to create a XML which is easier to handle (haven't found one, yet)
As mentioned by myself in the comments of the question:
I also don't see anything in the source code for this (maybe this is the reason why it doesn't work??)
I tried to place the XMLElement into an existing element on my webpage (like this:
var XMLElement = document.createElement("DataElementsCalc");
jQuery('.basicData').append(XMLElement);
in which basicData is a class of an existing element).
Now i do get a list of all elements ordered on the groups i wanted.
Final version
var XMLElement = jQuery("<DataElementsCalc/>");
jQuery('.basicData').append(XMLElement);
jQuery(xml).find("DataElements " + topNodes + filter).each( function()
{
aantalElementsTotal++;
var dataSetTemp = this.parentNode;
if(calculation1 != "")
{
var groupKeys = getGroups(dataSetTemp,calculation1);
var tempXML = XMLElement;
var groupId = '';
jQuery(groupKeys).each(function (key,value) {
var tempValue = 'a' + value.toLowerCase().replace(/\W/g, '');
groupId += 'a' + value.toLowerCase().replace(/\W/g, '');
if(jQuery("#" + groupId).length > 0)
{
tempXML = jQuery("#" + groupId);
}
else
{
var Node = jQuery("<"+tempValue+"/>");
jQuery(Node).attr('id', groupId);
jQuery(tempXML).append(Node);
tempXML = Node;
}
});
var Node = jQuery("<InfoSet/>");
var x = dataSetTemp.childNodes;
for (i=0; i < x.length; i++)
{
if(x[i].nodeType == 1)
{
var tempElement = jQuery("<"+x[i].nodeName+"/>");
jQuery(tempElement).text(x[i].childNodes[0].nodeValue);
jQuery(Node).append(tempElement);
}
}
jQuery(tempXML).append(Node);
}
});
i need to retrieve a value from an URL in JS, my problem is in this url, the element is repeated at least twice with different value. What i need is the last one.
Example :
http://randomsite.com/index.php?element1=random1&element2=random2&element1=random3
and what i want is "random3" and NOT "random1"
I've tried url.match(/.(\&|\?)element1=(.?)\&/)[2];
But it always gives me the first one :(
I don't have the possibility to change how the url is written as this is for a browser extension.
var ws = "http://randomsite.com/index.php?element1=random1&element2=random2&element1=random3",
input = ws.split("?")[1].split("&"),
dataset = {},
val_to_find = "element1";
for ( var item in input){
var d = input[item].split("=");
if (!dataset[d[0]]){ dataset[d[0]] = new Array(); dataset[d[0]].push(d[1]); }
else{
dataset[d[0]].push(d[1]);
}
}
console.log("item: ", dataset[val_to_find][dataset[val_to_find].length -1]);
return dataset[val_to_find][dataset[val_to_find].length -1];
http://jsfiddle.net/wMuHW/
Take the minimum value (other than -1) from urlString.lastIndexOf("&element1=") and urlString.lastIndexOf("?element1="), then use urlString.substring.
Or alternately, split the string up:
var parts = urlString.split(/[?&]/);
...which will give you:
[
"http://randomsite.com/index.php",
"element1=random1",
"element2=random2",
"element1=random3"
]
...then start looping from the end of the array finding the first entry that starts with element= and grabbing the bit after the = (again with substring).
You could;
for (var result, match, re = /[&?]element1=(.+?)(\&|$)/g; match = re.exec(url);) {
result = match[1];
}
alert(result);
Id try keeping a nested array of duplicate elements
function parseQueryString() {
var elements = {},
query = window.location.search.substring(1),
vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('='),
key = decodeURIComponent(pair[0]),
value = decodeURIComponent(pair[1]);
if (elements.hasOwnProperty(key)) {
elements[key].push(value);
}
else {
elements[key] = [value];
}
}
}
Used on: www.example.com?element1=hello&element2=test&element1=more
Would give you the object:
{
element1: [
"hello",
"more"
],
element2:[
"test"
]
}