waterMelon
waterMelon

Reputation: 15

Return highest number and associated name from object in Javascript

This is my react app - https://i.sstatic.net/9VpCd.jpg

I'm trying to return the highest and lowest latitude and the name of the city. I have it working to return the latitude but the name is coming up undefined... It is weird because in my jest testing, my function is working fine. I'm using React, Javascript and my API to load and store data... What I'm doing in this function is putting all the latitudes into an array then doing Math.max then looping through this.cities to see which latitude in the object matches the highest latitude that we put into mostNorthLat, then getting the name of the city that matches the lat.

This is my javascript file with my pure functions:

//Loads cities in API and injects it into this.cities

    async loadCities() {
            const data = await postData(url + "all");
            //Create a dictionary of cities and keep track of the last key
            const cities = {};
            data.forEach(x => {
                cities[x.key] = x;
                this.lastKey = (x.key > this.lastKey) ? x.key : this.lastKey;
            });

            this.cities = cities;
        }

//This is my add function I use for adding new cities to my API, which then React uses to create my cards for each object in my cities object in the API

    async addOrUpdate(city) {
            let theUrl;

            if (city.key) {
                theUrl = url + 'update'
            } else {
                theUrl = url + 'add'
                this.lastKey++;
                city.key = this.lastKey;
                city.population = Number(city.population)
            }
            await postData(theUrl, city);
            this.cities[city.key] = city;
    }

//My functions that are running the most North and most South

    getMostNorthern() {
            let latitudeArray = []
            let theName;
            for (let key in this.cities) {
                latitudeArray.push(this.cities[key].latitude)
            }
            let mostNorthLat = Math.max.apply(null, latitudeArray);
            for (let key in this.cities) {
                if (mostNorthLat === this.cities[key].latitude) {
                    theName = this.cities[key].name
                }
            }
            if (this.length() > 0) {
                return `${theName} at ${mostNorthLat}°`
            }
        }

    getMostSouthern() {
            let latitudeArray = []
            let theName;
            for (let key in this.cities) {
                latitudeArray.push(this.cities[key].latitude)
            }
            let mostSouthLat = Math.min.apply(null, latitudeArray);
            for (let key in this.cities) {
                if (mostSouthLat === this.cities[key].latitude) {
                    theName = this.cities[key].name
                }
            }
            if (this.length() > 0) {
                return `${theName} at ${mostSouthLat}°`
            }
        }

Here is what a city object looks like:

    {
        "key": 1, 
        "latitude": "1", 
        "longitude": "1", 
        "name": "City", 
        "population": 49500
    }, 

Each city object is held in a main object called 'cities'

    constructor() {
            this.cities = {};
            this.lastKey = 0;
        }

So this is an what 3 cities inside the object looks like:

    {
            '1': City {
              name: "Gangster's Paradise",
              latitude: -15,
              longitude: 5,
              population: 10,
              key: 1
            },
            '2': City {
              name: 'Pho City',
              latitude: 5,
              longitude: 52,
              population: 1050,
              key: 2
            },
            '3': City {
              name: 'Frisbee Land',
              latitude: 0,
              longitude: 12,
              population: 1000000,
              key: 3
            }
    }

My React portion:

    function CitiesApp() {
       const classes = useStyles();
       const [message, setMessage] = useState('')

       const [count, setCount] = useState(0)

       const [total, setTotal] = useState()
       const [northest, setNorthest] = useState()
       const [southest, setSouthest] = useState()

       useEffect(() => {
          //Load cities from the API - re-render when count is updated
          async function fetchData() {
             try {
                await cityCtrl.loadCities()
                updateSummary()
             } catch (e) {
                userMsg('Turn on the server')
             }
          }
          fetchData();
       }, [count]);

       async function onSave(city) {
          await cityCtrl.addOrUpdate(city)
          setCount(count + 1)
          updateSummary()
       }

       async function deleteCard(thekey) {
          await cityCtrl.deleteCard(thekey)
          setCount(count + 1)
          updateSummary()
       }

       async function moveIn(thekey, num) {
          await cityCtrl.movedIn(thekey, num)
          setCount(count + 1)
          updateSummary()
       }

       async function moveOut(thekey, num) {
          await cityCtrl.movedOut(thekey, num)
          setCount(count + 1)
          updateSummary()
       }

       function updateSummary() {
          setTotal(cityCtrl.getTotalPopulation())
          setNorthest(cityCtrl.getMostNorthern())
          setSouthest(cityCtrl.getMostSouthern())
       }
    }

Upvotes: 1

Views: 84

Answers (3)

sbolel
sbolel

Reputation: 3526

Looks like you've found an answer.

Here's an alternative approach to finding the largest and smallest values by sorting arrays of values.

This approach uses a functional approach with regular objects, rather than an object-oriented approach with classes. Note that this would require a refactoring of your code since you're currently using City object instances.

const cities = {
  '1': {
    name: 'Gangster\'s Paradise',
    latitude: -15,
    longitude: 5,
    population: 10,
    key: 1
  },
  '2': {
    name: 'Pho City',
    latitude: 5,
    longitude: 52,
    population: 1050,
    key: 2
  },
  '3': {
    name: 'Frisbee Land',
    latitude: 0,
    longitude: 12,
    population: 1000000,
    key: 3
  }
}

// statically defined keys that the city objects contain
const cityKeys = [
  'name',
  'latitude',
  'longitude',
  'population',
  'key'
]

// convert an array of values back into a city object
const cityArrayToObject = (city) => city.
  reduce(
      (accumulator, current, currentIndex) =>{
        accumulator[cityKeys[currentIndex]] = current
        return accumulator
      }, {}
  )


// sort the arrays by latitude smallest to largest
// and then returns the first and last elements, converted
// back to an object using the function above.
// The array of values of cities looks like this before being sorted:
/*
[
  [ "Gangster's Paradise", -15, 5, 10, 1 ],
  [ 'Frisbee Land', 0, 12, 1000000, 3 ],
  [ 'Pho City', 5, 52, 1050, 2 ]
]
*/
const sortedCities = Object
  // make an array out of the keys of the city
  .keys(cities)
  // map that array to a new array of values for each key
  .map((value, index) => Object.values(cities[value]))
  // then sort that array by the latitude, which is in index 1
  .sort((a, b) => a[1] - b[1])

// take out the last item (pop), which is the largest
const largest = cityArrayToObject(sortedCities.pop())

// take out the first item (shift) which is the lowest
const smallest = cityArrayToObject(sortedCities.shift())

console.log('Largest:', largest, 'Smallest:', smallest)

Which outputs:

Largest: {
  name: 'Pho City',
  latitude: 5,
  longitude: 52,
  population: 1050,
  key: 2
} 
Smallest: {
  name: "Gangster's Paradise",
  latitude: -15,
  longitude: 5,
  population: 10,
  key: 1
}

Note that this doesn't account for the case where two cities may have the same latitude.

Upvotes: 1

waterMelon
waterMelon

Reputation: 15

I have found the issue! The way I had this set up was using defaultValue in react and initially, any new city has default ' ' blank value. Then changes once I submit the form... however if I put in a number it would just be in the string so I had to convert the longitude and lat to a Number just as I did with population and then my functions worked.

    async addOrUpdate(city) {
        let theUrl;

        if (city.key) {
            theUrl = url + 'update'
        } else {
            theUrl = url + 'add'
            this.lastKey++;
            city.key = this.lastKey;
            city.population = Number(city.population)
            city.latitude = Number(city.latitude)
            city.longitude = Number(city.longitude)
        }
        await postData(theUrl, city);
        this.cities[city.key] = city;
    }

Upvotes: 0

Ethan Lipkind
Ethan Lipkind

Reputation: 1156

I think you could simplify things by doing all your work in a single loop through this.cities:

getMostNorthern() {
  if (!this.cities.length) return '';
  let mostNorthernCity;
  for (const key in this.cities) {
    if (!mostNorthernCity) {
      mostNorthernCity = this.cities[key];
      continue;
    }
    if (this.cities[key].latitude > mostNorthernCity.latitude) {
      mostNorthernCity = this.cities[key];
    }
  }
  return `${mostNorthernCity.name} at ${mostNorthernCity.latitude}`;
}

getMostSouthern would work the same way but you might rename the variable to mostSouthernCity and use the less than comparator.

Upvotes: 1

Related Questions